How to Open a List Form in a Modal Dialog Box

Among the many improvements to the user interface in SharePoint 2010, modal dialog boxes stand out. Odds are good that you will see one wherever a page transition would be distracting and it makes sense for you to remain in context. For example, when you are working with list items, the display, edit, and new forms each open in a modal dialog box such as the one shown in Figure 1. The background of the main page darkens, indicating that it is inactive. The dialog box that floats over the main page has input focus, and it does not release focus until it is closed. You cannot return to the main page until you deal with the dialog box.

announce_modal 

Figure 1 – SharePoint 201 Modal Dialog Box

Modal dialog boxes such as the one shown in Figure 1 are not created by calls to the window.showModalDialog method as you might expect. SharePoint modal dialog boxes are are not window objects. They are iframe objects, and they are  created by a client-side dialog framework that is new in SharePoint 2010. The framework is written entirely in JavaScript (ECMAScript) and makes extensive use of the Microsoft Ajax library.

The centerpiece of the new dialog framework is the SP.UI.ModalDialog.showModalDialog method. (For a good quick reference, see Dallas Tester's blog post, Using the Dialog Platform.) The showModalDialog method gives you the option of providing content for the dialog box either by creating HTML on the fly or by supplying the URL to an existing page. The second option is what SharePoint uses to create dialog boxes for working with list items. They are created by passing the URL for a list form to the showModalDialog method. For example, the page that is loaded in the dialog box shown in Figure 1 is NewForm.aspx.  You could invoke the dialog with this JavaScript:

 var options = {
     title: "Announcements - New Item",
     url: "../Lists/Announcements/NewForm.aspx"
 };
     
 SP.UI.ModalDialog.showModalDialog( options );

In this blog post I explain how to do the same thing that the SharePoint user interface does—open a list form in a modal dialog box—but do it from any page on the site, not just the page that displays the list. All of my examples are written in JavaScript and deployed in a Content Editor Web Part. I show three ways of accomplishing the task, beginning with a very simple example—the "hello world" of dialog boxes—and then moving on to more sophisticated techniques.

Preparation

The examples in this post are designed to add a new item to a Survey list. I chose this list type because it suggests occasions when you might want to enable adding new list items in a location other than the main list page. In fact, you might not want your users to be able to see other survey responses at all, in which case you definitely will want to invoke the add item dialog box out of the list context.

On my development instance of SharePoint Foundation 2010, I have created a short survey called GBE (for "Graded Browser Engines"). You can use any survey you like, but you will need to change the title wherever it appears in the example code. In addition, development and testing will be easier if the settings for your survey list allow multiple responses from the same user.


Tip: To allow multiple responses, navigate to the survey's overview page (../Lists/<ListName> /overview.aspx). Click Settings and then Survey Settings. Under General Settings, click Title, description and navigation. In Survey Options, under Allow multiple responses select Yes.


For testing purposes, I have created a new Web Part Page, and I have added the page to the Site Pages library. I have also added a Content Editor Web Part (CEWP) to the page.

Rather than struggle with the CEWP's editor, I like to use the Content Link to point to a content file that I have uploaded to the Site Assets library. My Site Assets library has a folder named JavaScript. Permissions on the folder give Full Control to Creator/Owner and Read permission to everyone else. This folder is where I upload the file that the Content Link points to.

How to Get the URL for a List Form

For the first two examples, you need the site-relative URL for the list form that you want to open in a modal dialog. (The third example uses SharePoint 2010 client object model APIs to get the URL.)

The names of the new, edit, and display forms for a list are defined by Form elements in the list's Schema.xml file. Look for this file in the {SharePointRoot}\TEMPLATE\FEATURES\ListName folder of your SharePoint installation. List forms are ASPX pages, and they are normally provisioned in the list's root folder.  For example, the Schema.xml file for the Announcements list defines the new form as NewForm.aspx. Thus you can expect to find the form for a new item at the site-relative URL, "/Lists/Announcements/NewForm.aspx".

If you are using Firefox, you have an alternative method that is much quicker. Just go to the page where the target list is displayed and add, edit, or display a list item in the normal way. When a modal dialog box opens, right-click on the page that is loaded in the dialog box frame, point to This Frame on the context menu, and then click Open Frame in New Window.  Get the URL from the browser's address bar. Remove any query string that is appended to the URL, and you are set.


Note:   If you were to apply this method to the dialog box that is shown in Figure 1, you would get a URL that ends "/Lists/Announcements/NewForm.aspx?RootFolder=&IsDlg=1". SharePoint adds the query string parameter "IsDlg=1" when it opens a page in a modal dialog box. As you might expect, the parameter indicates that the target is a dialog box. In this case, page elements such as the title row and the left navigation panel are styled as hidden or omitted altogether when the page is served. Page elements that should not show in a dialog box are tagged with a special CSS class "s4-notdlg" in the master page that is attached to NewForm.aspx. 


First Try » Hello, Modal Dialog Box

SharePoint modal dialog boxes are created by calls into a single JavaScript library, SP.UI.Dialog.js. You can find this file in the {SharePointRoot}\TEMPLATE\LAYOUTS folder. (The file is minified for performance. If you want to browse, get the more readable corresponding file, SP.UI.Dialog.debug.js.) Every page that has v4.master as its master page contains a link to SP.UI.Dialog.js.

The member of the library that most concerns our work here is the SP.UI.ModalDialog.showModalDialog method. Before you call this method, you need to define some options, such as the title of the dialog box and the URL of the page that you want to load. Although you can define dialog options  by creating a new SP.UI.DialogOptions object and setting its properties, the method also accepts an ordinary JavaScript object. What is important is that the object that you pass to showModalDialog has named properties that the method recognizes. For a complete list of property names, see Dallas Tester's blog post, Using the Dialog Platform.

If your list of options is short, you can simply pass an object literal to showModalDialog as the onclick handler in the following example does:

  
 <div id="displayDiv">
     <p>Please help us with our survey. Take our poll now!</p>
     <input onclick="javascript:SP.UI.ModalDialog.showModalDialog({ url: '../Lists/GBE/NewForm.aspx', title: 'User Survey' }); return false;" id="btnVote" type="button" value="Vote" />
 </div>
  

When the example is uploaded to Site Assets and loaded into the CEWP, it produces the modal dialog shown in Figure 2.

voteSimple

Figure 2 – Hello, Modal Dialog Box

The example works well enough, but it has a major shortcoming when measured against standards of good user interface design. It does not give users any feedback. When you click Finish and the dialog box closes, nothing on the page indicates that your response to the survey has been accepted. Providing visual feedback from a dialog action is easy to implement, but to do the job we need a callback function.

Second Try » Using a Callback Function and Dialog Result

The callback mechanism for showModalDialog is straightforward. You just assign a function to the dialogReturnValueCallback property of the options object that you pass to showModalDialog. The callback function is invoked immediately after the dialog box closes.

List forms close a dialog box by calling the SP.UI.ModalDialog.commonModalDialogClose method. For example, the form's OK button might handle the click event with this code:

  
 SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.OK, someValue);
  

The form's Cancel button might handle its click event like this:

  
 SP.UI.ModalDialog.commonModalDialogClose(SP.UI.DialogResult.cancel);
  

In either case, your callback function can expect two arguments. The second argument could be any value and might be undefined; it depends on how the list form is designed. The first argument is of more interest to us here.  SP.UI.DialogResult is an enumeration that SP.UI.Dialog.debug.js defines in the following way:

  
 SP.UI.DialogResult = function() {}
 SP.UI.DialogResult.prototype = {
     invalid: -1, 
     cancel: 0, 
     OK: 1
 }
 SP.UI.DialogResult.registerEnum('SP.UI.DialogResult', false);
  

In other words, if the first argument to our callback function is greater than zero, then the user clicked OK or the equivalent. Armed with this knowledge, we can revise the handler for the Vote button's click event.

  
 <div id="displayDiv">
     <p>Please help us with our survey. Take our poll now!</p>
     <input onclick="javascript:showDialog('../Lists/GBE/NewForm.aspx');" id="btnVote" type="button" value="Vote" />
 </div>
  
 <script language="javascript" type="text/javascript">
  1:  
  2:     function showDialog( /* string */ url ) {
  3:         
  4:         // Do some argument checking.
  5:         if ( typeof url != "string" )
  6:             throw new Error.argument("url", "Expected a string.");
  7:                
  8:         // Define dialog box options.
  9:         var options = {}; 
  10:         options.title = "User Survey";
  11:         options.url = url;
  12:         options.dialogReturnValueCallback =
  13:  
  14:                 function (dialogResult, returnValue) {
  15:  
  16:                     // If the user clicked the Finish button (equivalent to OK)
  17:                     if (dialogResult) {
  18:  
  19:                         // Use an Ajax shortcut method to modify the DIV element.
  20:                         $get('displayDiv').innerHTML = "<p>Thank you for voting!</p>";
  21:                     }
  22:                 };
  23:  
  24:         // Display the dialog box.
  25:         SP.UI.ModalDialog.showModalDialog(options);
  26:     }
  27:    
 </script>

The callback function is defined (on line 14) as a function literal. You can use a named function. I used a literal because I did not intend to call the function anywhere else in the code. The only thing that the function does is test the dialog result to see if the user clicked Finish and, if so, the function replaces the content of the Div tag with a feedback message.

Notice that line 20 calls an Ajax shortcut for the document.getElementById method. Any SharePoint page that uses v4.master is Ajax-enabled.

When the example is uploaded to Site Assets and loaded into the CEWP, it creates the same modal dialog as shown in Figure 2. If you click Finish, the dialog closes and the page is updated as shown in Figure 3.

feedback

Figure 3 – Feedback

This is an improvement over the previous example, but it still needs work. For one thing, the URL for the list form is hard-coded when it doesn't need to be. The code could use the client object model to query the list and return the value of the List.DefaultNewFormUrl property. That one improvement would make the code a bit more robust. However, there is a much bigger problem lurking. What if the survey list settings are configured so that multiple responses from the same user are not allowed? A user who clicks the Vote button a second time gets the ugly mess shown in Figure 4.

oops

Figure 4 – "You are not allowed to respond again to this survey."

If we were writing server code (such as the code behind this list form), we could anticipate the exception and handle it gracefully. But we are writing client-side code, so what we need to do is determine whether the current user has already voted and, if so, present a different user interface by removing the Vote button from the page. Users who have voted are not given the opportunity to vote again, so they never see the error message. Problem solved.

To implement both of these improvements, we need to give our code access to list data by calling into the SharePoint client object model.

Third Try » A List Data Driven User Interface

The last example shows how to change the interface that is presented to a user based on information obtained from a client-side query against list data. The SharePoint 2010 Foundation SDK has many examples that demonstrate how to query list data. Two good resources for the kind of work we are doing here are How to: Work with Users and Groups and How to: Retrieve List Items.

We need three pieces of information:

  • The URL for the survey list's default new item form.
  • The login name of the current user.
  • Whether any item on the survey list shows the current user as Author.

We also want to hold round-trips to a minimum, so we should try to get all the information that we need in a single query against the list.

Revised user interface code might look like this:

  
 <div id="displayDiv" style="visibility:hidden" >
     <p>Please help us with our survey. Take our poll now!</p>
     <input id="btnVote" type="button" value="Vote" />
 </div>
 <script language="javascript" type="text/javascript">
  1:  
  2:     // Change the value to match the title of your list.
  3:     var listTitle = "GBE"; 
  4:  
  5:     // Invokes the getData function.
  6:     ExecuteOrDelayUntilScriptLoaded(getData, "sp.js");
  7:  
  8:     var currentUser, list, listItems;
  9:     function getData() {
  10:  
  11:         var ctx = new SP.ClientContext.get_current();
  12:  
  13:         // Get the current user.
  14:         this.currentUser = ctx.get_web().get_currentUser();
  15:         ctx.load(this.currentUser);
  16:  
  17:         // Get the URL for the default new item form.
  18:         this.list = ctx.get_web().get_lists().getByTitle(this.listTitle);
  19:         ctx.load(this.list, "DefaultNewFormUrl");
  20:  
  21:         // Get the list item collection.
  22:         var caml = new SP.CamlQuery();
  23:         this.listItems = this.list.getItems(caml);
  24:         ctx.load(this.listItems, "Include(Author)");
  25:  
  26:         ctx.executeQueryAsync(
  27:                                 Function.createDelegate(this, success),
  28:                                 Function.createDelegate(this, failure)
  29:                               );
  30:     }
  31:  
  32:     function success(sender, args) {
  33:  
  34:         if ((this.currentUser) && (this.listItems)) {
  35:  
  36:             var displayDiv = $get("displayDiv");
  37:  
  38:             var userName = this.currentUser.get_title();
  39:             var hasVoted = false;
  40:  
  41:             // Enumerate over the list items.
  42:             var itemsEnumerator = this.listItems.getEnumerator();
  43:             while (itemsEnumerator.moveNext()) {
  44:  
  45:                 var item = itemsEnumerator.get_current();
  46:                 // If the list has an item where the Author is the current user...
  47:                 if (userName == item.get_item("Author").get_lookupValue()) {
  48:                     // then the current user has voted.
  49:                     hasVoted = true;
  50:                     break;
  51:                 }
  52:             }
  53:  
  54:             if (hasVoted) {
  55:  
  56:                 displayDiv.innerHTML = "You have already voted.";
  57:             }
  58:             else {
  59:                 
  60:                 // Attach an event handler to the Vote button.
  61:                 $addHandler($get("btnVote"), "click", Function.createDelegate(this, showDialog));
  62:             }
  63:             
  64:             // Show the UI.
  65:             displayDiv.style.visibility = "visible";
  66:          }
  67:     }
  68:  
  69:     function failure(sender, args) {
  70:         
  71:         // Display the error message on the page.
  72:         var displayDiv = $get("displayDiv");
  73:         displayDiv.innerHTML = args.get_message();
  74:         displayDiv.style.visibility = "visible";
  75:     }
  76:  
  77:     function showDialog() {
  78:  
  79:         // Define dialog box options.
  80:         var options = {};
  81:         options.title = 'User Survey';
  82:         options.url = this.list.get_defaultNewFormUrl();
  83:         options.dialogReturnValueCallback =
  84:  
  85:                 function (dialogResult, returnValue) {
  86:  
  87:                     // If the user clicked the Finish button (equivalent to OK)
  88:                     if (dialogResult) {
  89:  
  90:                         // Detach the Vote button's click handler and dispose of the delegate.
  91:                         $clearHandlers($get("btnVote"));
  92:  
  93:                         // Replace the content of the DIV element.
  94:                         $get('displayDiv').innerHTML = '<p>Thank you for voting!</p>';
  95:                     }
  96:                 };
  97:  
  98:         // Display the dialog box.
  99:         SP.UI.ModalDialog.showModalDialog(options);
  100:     }
 </script>
  
  

When the example is uploaded to Site Assets and loaded into the CEWP, it produces the same modal dialog shown in previous examples, and the user gets the same feedback after voting. What is different is what is presented to a user who has already voted. That outcome is shown in Figure 5.

voteClientOM

Figure 5 – "You have already voted."

Next Steps

Rather than deploying this solution on a Content Editor Web Part, we really ought to design a custom Web Part that an end user could easily configure to work with any survey list. In that case, list data could be queried by server-side code, reducing traffic over the network. I will leave that exercise for you to do on your own. Before you undertake it, I recommend that you look over How to: Display a Page as a Modal Dialog Box. The article has good information about deploying JavaScript resources and creating a custom Web Part that uses them.