Tuesday, March 5, 2013

Modal Dialogs, ECMAScript, and Client/Server Interaction

I built a little Modal Dialog Application Page, launched by a Ribbon button, that would take what a user wrote in a text box and send it in an email to all of the "Assigned To"s in a task list.

My client wanted the ability to check the boxes next to the tasks, and have my Modal page send a message to just those users who were checked. Fairly simple, right? Not so much. You see, the ribbon button is controlled by SharePoint 2010's ECMA Script, where my "Notify" page is controlled by .NET managed code. So... How do we transfer JavaScript ECMA script to .NET managed code? It is fairly elementary to do through Silverlight's API, but what about simple JavaScript and .NET?

It takes a bit of cheating to get it done. First we need to look at how we launch a Modal Dialog Page. It is done in JavaScript. I am using a ribbon button to launch mine so the JavaScript is contained in the button's Custom Action elements file. Anyway, the modal dialog code involves creating calling the SP.UI.ModalDialog.showModalDialog method, and passing in the options that we want for the page. It looks like this:
var editOptions = SP.UI.$create_DialogOptions();
    editOptions.title = "Notify User";
    editOptions.url = "_layouts/SolutionFolder/Notify.aspx";
    editOptions.height = "600";
    editOptions.width = "500";
    editOptions.allowMaximize = "true";
    editOptions.showClose = "true";
    editOptions.args = args;
    editOptions.dialogReturnValueCallback = Function.createDelegate(null, CloseCallBack);
    SP.UI.ModalDialog.showModalDialog(editOptions);

Very straight forward. What we need to pay attention to here is the "args" option. This option allows us to pass objects from the originating script to the modal page. In my case, what I need is the ID if the SPList I am using and the individual IDs of the checked list items.

Fortunately, Microsoft has thought of that and we can obtain those very pieces of information straight away. We need only create a context and call two methods in the sp.js file. For the SPList ID, I call SP.ListOperation.Selection.getSelectedList() and for an array of the selected list item IDs I call SP.ListOperation.Selection.getSelectedItems(). Easy!
Next, I set those objects in to the options args object. It looks like this:
var context = SP.ClientContext.get_current();
var listId = SP.ListOperation.Selection.getSelectedList();
var items = SP.ListOperation.Selection.getSelectedItems();
var args = {
        listId: listId,
        items:  items
           };
This bit of code, of course, goes before you create the DialogOptions object.

Cool! Now I have my options, I launch my Notify.aspx page, and we are good! Not so fast! We still have to get the args object out of the JavaScript client world and in to the .NET server world. Now is where we get fancy.

In ASP.NET, how do we get information from the user on the client to the server managed code? We have some sort of a control that passes its user manipulated values to the back end via some sort of user action, like a button click. The same is true here. We create a generic "input" control on our page, and set the value of that control to be the args object. Yay!!

So, on our Modal Page we create a little bit of JavaScript. First, we call the ECMA Script method that will get the data we passed in the args object. We will then use JavaScript to set the value of our input control to be that of the args object.

First we need the input control:

Easy enough, right? Note the the runat is set to SERVER. This is very important. This control must be a SERVER control, otherwise we will not be able to get the value out. Microsoft has provided a pre-made method to get the args out, the SP.UI.ModalDialog.get_childDialog().get_args() method.
Now, inside a "script" tag on the modal page, we use the following JavaScript:
ExecuteOrDelayUntilScriptLoaded(function () {
            var args = SP.UI.ModalDialog.get_childDialog().get_args();
            document.getElementById('<%= args.ClientID %>').value = JSON.stringify(args);
        }, "sp.js")        

Notice here a couple of things. The entire bit of code is run in the ExecuteOrDelayUntilScriptLoaded delegate. That means that the entire page has to be loaded before you can star messing with any values. That means that the stuff we want from the SPList can not be gathered in the Page_Load method. You must wait until the page has been completely rendered!!!!
Second, you will notice the funky stuff in the document.getElementById function. This is because .NET will change the ID of all server controls. You need to know what the ID of the control is so that you can set the value, so... You have to call the managed code to get the ID. Yet another reason why you have to wait until the page is completely rendered before you can get to the args value.

This script, on the modal page, sets the value of the control. We are now able to get the args objects, but first we need to set up a couple of classes to format the data.

public class Args {
     public string ListId { get; set;}
     public System.Collections.Generic.List<Item> Items { get; set; }
}

public class Item {
     public int Id { get; set; }
}

Now we have some fun. We grab the args information then format it by using the System.Web.Script.Serialization.JavaScriptSerializer. It puts it in to a C# object format, integers for the integers and a string for the ID objects.

var javaScriptSerializer = new JavaScriptSerializer();
string json = args.Value;
var SelectedValues = javaScriptSerializer.Deserialize<Args>(json);

Now the SelectedValues object contains a string of the listID and a List of the item IDs. From these we can now plug in the values using a foreach loop in to the rest of the Notify code to send messages out to those items that were checked.

You can, of course, plug other simple objects in to the args object, just as long as there is some analogous type on the Managed side of the fence.

No comments:

Post a Comment