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

Many page activities in SharePoint 2010 open a modal dialog that allows you to complete a task without navigating away from the current page. When the dialog opens, the background darkens (the so-called lightbox effect) indicating that the page is inactive. You must deal with the dialog before you can return to the page.

Good examples are the modal dialogs that open when you create, view, or edit an item in a SharePoint list. Each of these dialogs loads a SharePoint application page (NewForm.aspx, DispForm.aspx, or EditForm.aspx) in a popup window. Although the popup window displays what is, technically speaking, a new page, the new page appears to open in the context of the page that you were on. The effect is as if you had zoomed in, rather than navigated away.

image0

When SharePoint 2010 creates a modal dialog, it employs a new client-side dialog framework that you can tap into as well. For the most part, the functionality is encapsulated in the SP.UI.Dialog and SP.UI.ModalDialog classes of the ECMAScript Class Library. The dialog classes are implemented in the file SP.UI.Dialog.js in the LAYOUTS folder of your SharePoint installation. (The source code is minified for performance; if you want to browse, get the more readable companion file, SP.UI.Dialog.debug.js.)

It is not hard to imagine situations where you might want to invoke a modal dialog from an InfoPath form. A classic example is a data-picker control that pops up a calendar so that the person filling out the form can browse to and select a date. Another example is where the form calls for information that is not readily available to the person who is filling it out, such as a link to an item in a SharePoint list. In this case, you might want to open a dialog that allows the user to navigate to or search for the information that the form requires without leaving the context of the form.

In this five-part series we develop an example application that demonstrates how to open a SharePoint modal dialog from an InfoPath form.

Example Application

Human Resources at Contoso has requested a browser-based form that each department can fill out as a first step in planning for the coming fiscal year. The form will be used to gather information about the department's current resources and forecast needs. One field in the proposed form asks for a link to a department org chart.

To fulfill this request, you decide to design a Microsoft InfoPath form hosted on an internal SharePoint website. As you design the form, you realize that in order to fill in the field that asks for a link to org chart, the user must navigate away from the form to get the URL. You want to avoid any situation that causes a user to leave the form, so you decide to give the form the ability to open a modal dialog that allows the user to search for and select the required file.

Application Architecture

The example application has the following components:

  • A browser-enabled InfoPath form.

    The title of the form is "Contoso Resource Planning." It contains a label ("Organization Chart"), a hyperlink field ("URL"), and a button ("Search"). The button click event is handled by form code that raises the NotifyHost event and passes an XPath expression for the field.

  • A Web Part containing an XMLFormView control.

    The InfoPath form is hosted by an XMLFormView control inside a custom Web Part. The XMLFormView control subscribes to the NotifyHost event and handles it by invoking a modal search dialog. When the dialog closes, the Web Part updates the field in the form.

  • A modal dialog.

    The modal search dialog is opened by a client script that executes in the context of the Web Part page. The script calls a method in the SP.UI.ModalDialog class to open a modal dialog and display a search application page. When the modal dialog closes, the result of the search is passed to a callback function that also executes in the context of the Web Part page.

  • A SharePoint application page.

    The user interface and the logic for the search application are implemented in a SharePoint application page. For the purpose of this demonstration, the application page implements a simple search among the items listed in a document library on the SharePoint website.

The relationship between the components is shown in the following diagram:

ApplicationArchitecture

Building the Application

We will develop the example application in five steps.

  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.

In today's post, we complete step one, “Create a Search Application Page.”

Prerequisites

You will learn more if you follow along on your own development website as we construct the example application. To complete the application, you need the following tools:

  • Microsoft Visual Studio 2010
  • Microsoft InfoPath Designer 2010
  • Microsoft Visual Studio Tools for Applications
  • A server running Microsoft SharePoint Server 2010

You also need to do some preparation on your development website. First, make sure that the root web of your SharePoint site collection has a Shared Documents library. Then populate the library with dummy documents that can be found by the search application. Use document names relevant to our scenario such as IT Department Org Chart FY11.docx, IT Department Policies.docx, and so on.

Although not required reading, the following articles contain background information that may help explain some of the techniques that we use:

Step 1: Create a Search Application Page

We begin by creating a search application page that allows a user to search for and select a document. Our project is not about implementing search, so the application that we create simply enumerates over the Shared Documents library. You can extend it later by implementing fullblown search.

We will build the search application page in two stages. First we create the user interface on the page. Then we implement the search logic in code behind the page.

To create the user interface for the search application page
  1. In Microsoft Visual Studio 2010, create a new project.

  2. In the New Project dialog, select SharePoint 2010 and the Empty SharePoint Project template.

  3. Name the project ModalHost. ApplicationPages. Then click OK.

  4. In the SharePoint Customization Wizard, click Deploy as a farm solution. Then click Finish.

  5. In Solution Explorer, right-click the project node, point to Add, then click New Item.

  6. In the Add New Item dialog, select the Application Page template. Name the page ModalHost.aspx. Then click Add.

  7. In the ModalHost.aspx application page markup, locate ContentPlaceHolderID="PlaceHolderMain". Place the insertion point on a blank line above the closing </asp:Content> tag. Then copy the following markup and paste it at the insertion point:

     <asp:TextBox runat="server" ID="Dialogvalue" CssClass="modalhiddenfield"></asp:TextBox>
    <div style="font-family: Calibri; font-size: 16px">
        <div>
            <div>
                <span style="vertical-align: middle;">Enter document name:</span>&nbsp;
                <asp:TextBox ID="SearchBox" runat="server" Width="242px" ToolTip="Search.."></asp:TextBox>
                <asp:ImageButton ID="Dosearch" runat="server" ImageUrl="~/_layouts/images/searchlogo.png"
                    ImageAlign="Top" OnClick="Dosearch_Click" Width="20px" AlternateText="search"
                    ToolTip="Search" />
            </div>
            <div style="padding-top: 10px">
                <div style="padding-bottom: 10px">
                    <asp:Label runat="server" Visible="false" ID="ResultCount"></asp:Label>
                </div>
                <SharePoint:SPGridView ID="ResultGrid" runat="server" AutoGenerateColumns="false"
                    ShowHeader="true" RowStyle-BackColor="#EBEBEB" HeaderStyle-BackColor="#C3C3C3"
                    AlternatingRowStyle-BackColor="#F6F6F6" EnableTheming="true" ShowHeaderWhenEmpty="true"
                    AutoGenerateSelectButton="true" SelectedRowStyle-BackColor="#EDE275" AllowSorting="true">
                    <Columns>
                        <asp:HyperLinkField HeaderText="Title" DataNavigateUrlFields="Url" DataTextField="Title"
                            ShowHeader="true" HeaderStyle-BackColor="#C3C3C3" HeaderStyle-ForeColor="#000000" />
                        <asp:BoundField HeaderText="Created By" DataField="CreatedBy" ShowHeader="true" 
                            HeaderStyle-BackColor="#C3C3C3" HeaderStyle-ForeColor="#000000" />
                    </Columns>
                </SharePoint:SPGridView>
            </div>
        </div>
        <!-- Insert the modal dialog OK and Cancel buttons here--->
    </div>
    

    This markup creates a user interface that has the controls shown in the following image:

    image1a

Now we need to make the controls do something. Basically, we need to handle two events: the Click event raised by the ImageButton control, and the SelectedIndexChanged event raised by the the SPGridView control.

When the ImageButton is clicked, our handler needs to:

  • Get a reference to the Shared Documents library on the current website.
  • Query the library for items with titles that contain the search terms.
  • Display the result count in the Label control.
  • Display the result set in the SPGridView control.

When the user selects an item in the SPGridView control, the handler for the SelectedIndexChanged event needs to:

  • Extract information that we can later use to create a hyperlink from the selected document's metadata.
  • Store the document metadata in the DialogValue text box.

Later, we will write code that passes the document metadata to a callback function that executes in the context of the search application's consumer, a Web Part.

To add code for the search application page
  1. Click anywhere on the markup for ModalHost.aspx. Then press F7 to view the page code.

  2. At the top of the file, add the following using statements:

     using System.Web.UI;
    using System.Linq;
    using System.Collections.Generic;
    using System.Web.UI.WebControls;
    using Microsoft.SharePoint.Utilities;
    
  3. Delete contents of the ModalHost class body. Then copy the following code and paste it into the class body.

     protected void Page_Load(object sender, EventArgs e)
    {
        //Register the SPGridView SelectedIndexChanged event.
        ResultGrid.SelectedIndexChanged += new EventHandler(ResultGrid_SelectedIndexChanged);
    }
    
    //Fires when the search button is clicked.
    protected void Dosearch_Click(object sender, ImageClickEventArgs e)
    {
        // Declare a variable to store the website's url.
        string url = string.Empty;
        // Reset the hidden text box to empty.
        Dialogvalue.Text = string.Empty;
    
        try
        {
            // Get the document library object along with the site url.
            SPList documents = this.GetDocumentLibrary(out url);
            if (documents != null && documents.Items.Count > 0)
            {
                if (!string.IsNullOrEmpty(SearchBox.Text.Trim()))
                {
                    // Create a collection of the custom class Result by enumerating over library items.
                    List<Result> results = (from document in documents.Items.OfType<SPListItem>()
                                            where document.Name.ToLower().Contains(SearchBox.Text.ToLower())
                                            select new Result
                                            {
                                                Title = document.Name,
                                                // Create a complete url for the document.
                                                Url = string.Concat(url, "/", document.Url),
                                                // Capture 'Created By' from document metadata and trim off the id.
                                                CreatedBy = this.GetUserName(document["Created By"].ToString()),
                                            }).ToList();
    
                    // Display the result count.
                    ResultCount.Text = string.Concat(results.Count.ToString(), " items found.");
                    ResultCount.Visible = true;
                    // Bind the gridview with the result object collection.
                    ResultGrid.DataSource = results;
                    ResultGrid.DataBind();
                    // Reset the selected gridview row from previous search.
                    ResultGrid.SelectedIndex = -1;
                    // Display the gridview is there are results to display.
                    if (results.Count > 0)
                    {
                        ResultGrid.Visible = true;
                    }
                    else
                    {
                        ResultGrid.Visible = false;
                    }
                }
                else
                {
                    // Ask the user to enter a search term. 
                    ResultCount.Text = "Please enter one or more search words.";
                    ResultCount.Visible = true;
                    // Reset the gridview datasource if any of the required parameters are absent.
                    ResultGrid.DataSource = null;
                    ResultGrid.DataBind();
                    ResultGrid.Visible = false;
                }
            }
            else
            {
                // Tell the user that the search cannot connect to the content source. 
                ResultCount.Text = "Cannot connect to content source or the source is empty.";
                ResultCount.Visible = true;
                // Reset the gridview datasource if any of the required parameters are absent.
                ResultGrid.DataSource = null;
                ResultGrid.DataBind();
                ResultGrid.Visible = false;
            }
        }
        catch
        {
            throw;
        }
    }
    
    //Fires on a row is selected in the gridview.
    void ResultGrid_SelectedIndexChanged(object sender, EventArgs e)
    {
        try
        {
            // Check if an items has been selected.
            if (ResultGrid.SelectedIndex != -1)
            {
                // Get the selected row from the gridview.
                SPGridViewRow row = ResultGrid.SelectedRow as SPGridViewRow;
                if (row != null)
                {
                    // Create a hyperlink object to retrieve the selected document details.
                    HyperLink hyperlink = row.Cells[1].Controls[0] as HyperLink;
                    // Get the document title by removing the file extenion.
                    string docname = hyperlink.Text.Remove(hyperlink.Text.LastIndexOf("."));
                    // Encode the string hyperlink to url.
                    string docurl = SPEncode.UrlEncodeAsUrl(hyperlink.NavigateUrl);
                    // Create a ; separated string of docname and docurl and assign to a hidden text box.
                    Dialogvalue.Text = string.Concat(docurl, ";", docname);
                }
            }
        }
        catch
        {
            throw;
        }
    }
    
    //Gets the list object for the default Shared Documents library.
    private SPList GetDocumentLibrary(out string siteurl)
    {
        try
        {
            //Read the url from the query string.
            siteurl = this.GetQueryStringValue();
            if (!string.IsNullOrEmpty(siteurl))
            {
                //Create the site object based on the received url.
                using (SPSite site = new SPSite(siteurl))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        // Look for the Shared Documents library on the web. 
                        SPList list = web.Lists.TryGetList("Shared Documents");
                        if (list != null)
                        {
                            return list;
                        }
                    }
                }
            }
        }
        catch
        {
            throw;
        }
    
        return null;
    }
    
    // Retrieves the url from the page query string.
    private string GetQueryStringValue()
    {
        if (Page.Request.QueryString.Count > 0)
        {
            // Check if the page query string contains a url key.
            if (!string.IsNullOrEmpty(Page.Request.QueryString["url"]))
            {
                return Page.Request.QueryString["url"].ToString();
            }
        }
    
        return null;
    }
    
    // Trims the user name string by removing the ;#.
    private string GetUserName(string spusername)
    {
        int index = spusername.LastIndexOf("#");
        if (index != -1)
        {
            // Remove the id and # from the username string.
            spusername = spusername.Remove(0, index + 1); //"9;#UserName" 
        }
    
        return spusername;
    }
    
    // Custom class to store required document metadata.
    public class Result
    {
        public string Title { get; set; }
        public string Url { get; set; }
        public string CreatedBy { get; set; }
    }
    
  4. In Solution Explorer, right-click the project node for ModalHost.ApplicationPages. Then click Deploy.

Take a moment to look over the code.

-

**GetDocumentLibrary.** This method returns the SPList object that represents the Shared Documents library and uses an output parameter to return the URL of the current SharePoint site collection.

  
  

The method gets a reference to the list by instantiating an SPSite object to represent the site collection, getting the SPWeb object for the root website, and then querying the list collection for the SPList object that represents the Shared Documents library. This is all fairly standard practice for programming against SharePoint data.

  
  

Notably, the code uses the site collection URL to instantiate an SPSite object, but it does not get the URL from `SPContext.Current.Web.Url` as it might if it were running in the context of an ordinary content page or a Web Part. This code runs in the context of a SharePoint application page, where context information is not available. The standard practice in this case is for the consumer of the application page to pass required context information in parameters of a query string appended to the page URL.

  
  

The GetDocumentLibrary method gets the value of the query string parameter by calling the GetQueryStringValue method.

  
  • GetQueryStringValue. This method returns the value of the url parameter in the query string appended to the page URL. For example, given a page URL like this:

    https://teamsite/_layouts/ModalHost.ApplicationPages/ModalHost.aspx?url=https://teamsite

    The method returns this:

    https://teamsite

  • Result class. This custom class contains three properties to hold document metadata (Title, URL and Created By) that we are interested in. A collection of Result objects is bound to the GridView control to display the search results.

  • Dosearch_Click. This event handler enumerates over the items in the document library and generates a collection of Result objects matching the search criteria.

  • ResultGrid_SelectedIndexChanged. This event handler is invoked when a user selects a row inside the gridview. It creates a semicolon delimited string using the selected document’s title and URL. It then stores this string as the value of the Dialogvalue text box so that it can read by the consumer application.

You can navigate to the page that you have created by copying the following URL (with appropriate substitutions) into your browser's address bar:

 https://YOUR_SITE_NAME/_layouts/ModalHost.ApplicationPages/ModalHost.aspx?url=https://YOUR_SITE_NAME

The application page that displays should resemble the following image:

image2

Next Steps

We’re done for today. In Part 2 we build a modal dialog to display the application page.