Wednesday, 17 October 2012

Display multiple nested modal dialogs boxes using a custom action

Adding custom actions to SharePoint's ribbon and context menus is rather cool (I reckon), as well as rather easy! However, while adding an item to the ECB (Edit Control Block) / LIM (List Item Menu) the other day, I was getting an annoying JavaScript issue when closing the dialogs.

Firstly, a little more on what I was doing. From the LIM, I had a menu item that opens an application page in a modal dialog box for creating a new item (it uses a special content type and also creates associated items, hence the special application page). When the user submits the form, the new list items are created, and a second form (the default edit form for the new parent list item) is opened in a new dialog box, allowing the user to enter more information into the fields of a child list item that was created.

So far, so good. When the user clicks Save on the edit form, the form should close (all modal dialogs) and the list (page) should be refreshed to show the new items created.

1. Click on the custom action, which opens the application page in the first modal dialog

2. Enter the new controls (new item) title, and click Save (which creates two new items, a "control item", and an "action item" (which inherits certain field values from the control item), and opens a new dialog containing the default edit page for the new "action item"

3. User fills in the fields for the new "action item" and clicks OK

4. Both modal dialogs should be closed, and the list (parent page) should be refreshed.

The problem was, when the edit form was submitted, the modal dialogs would close ok, but the page wouldn't be refreshed, and instead a JavaScript error was raised.

Message: Function expected
Line: 1139
Char: 13
Code: 0
URI: http://my/_layouts/sp.ui.dialog.debug.js?rev=I4RtkztzINg%2B%2BPCPe%2FeQlw%3D%3D

To cut a long, boring and frustrating story short, this all came down to how I was calling the first modal dialog and forgetting to define the callback function in the custom action.

To get this working correctly, this is what I did;

Put the JavaScript into the URL element of the custom action that will open my application page in a SharePoint modal dialog box (which is where I went wrong initially, forgetting to define the callback function, hence the JavaScript error):

<CustomAction Title="New Control" Id="{1ac99a52-2fdd-4217-b7f4-664011f45251}" 
ImageUrl="/_layouts/images/inceriskregister/document-new32.png" 
Location="EditControlBlock" RegistrationId="0x01007006E66EE8DD45458AEC3EF515A0E6E101" 
RegistrationType="ContentType" Sequence="2" xmlns="http://schemas.microsoft.com/sharepoint/" 
Description="Add a new control for this risk." Rights="AddListItems" >
 <UrlAction Url="javascript:function displayStatus(dialogResult, returnValue) 
 {SP.UI.ModalDialog.RefreshPage(1); SP.UI.ModalDialog.commonModalDialogClose(1,1); }; 
 OpenPopUpPageWithTitle('/_layouts/inceriskregister/newcontrol.aspx?parentId={ItemId}&amp;
 ListId={ListId}&amp;webUrl={SiteUrl}&amp;',displayStatus,null,null,'Add New Control')" 
 />
</CustomAction>


As for my application page, it has the following JavaScript in the page head to handle opening the second modal dialog (after the user has entered a title on the form and clicked Save), and for closing itself:

<script type="text/javascript">
function inceOpenDialog(listEditFormUrl, id, listid) {
ExecuteOrDelayUntilScriptLoaded(function () 
{ intInceOpenDialog(listEditFormUrl, id, listid); }, 'sp.js');}

function intInceOpenDialog(listEditFormUrl, id, listid) {
  try {
   var editForm = listEditFormUrl + '?ID=' + id + '&ListId=' + listid + "";
   var options = {
    url : editForm,
    autoSize : true,
    showClose : true,
    allowMaximize : false,
    dialogReturnValueCallback : displayStatus
   };
   SP.UI.ModalDialog.showModalDialog(options);
  } catch(e) {
   alert(e);
  }
 }

function displayStatus(dialogResult, returnValue) {
  SP.UI.ModalDialog.RefreshPage(1);
  SP.UI.ModalDialog.commonModalDialogClose(dialogResult, returnValue);
 }

function inceCloseModalDialog(refresh) {
  SP.UI.ModalDialog.RefreshPage(1);
  SP.UI.ModalDialog.commonModalDialogClose(dialogResult, returnValue);
 }  
</script>


It always helps if you do thing right the first time. Unfortunately it sometimes takes a few hours of rooting around and getting increasingly frustrated just to find that you've implemented something incorrectly!