Tuesday, March 19, 2013

Generics Can Be a Pain...

I am working on a little program for my client and I am using Sytem.Collections.Generic.List<T>s. Since most of the time I talk about SharePoint I want to make it clear what kind of list I am talking about. I'll complicate things later. ;-)

Anyway, I have two lists, in this case both lists are of type List<SPUser>. One list represents a group of SPUsers that I want to assign tasks to in a task list. The other list represents the users who already have tasks assigned to them.
My requirement is that I can not duplicate users. So, once a user has a task assigned to them, I don't want to assign a new task to them in the same list.

So, I have these two lists of the same type. I need to exclude the users from one list who exist in the other list. Simple!!, someone calls, just use List<T>.Exclude! No muss no fuss!! BZZZZZZZZZZZZZZZ Wrong answer. List<T>.Exclude will only exclude objects that are EXACTLY alike. That means that they have to come from the same instance. Essentially, if the two objects are not in the exact same memory space, Exclude will fail. This is not very intuitive, because we with the meat memories say, well they are the exact same object so it should work! The computer says, nope, the first one is from instance A, occupying memory block A, and the second one is from instance B, occupying memory block B. B!=A therefore NOT THE SAME. It really really really sucks.

Ok, we accept that life is hard and we have to do some thinking in order to get what we want to happen. So how can we do it? We could use foreach loops to iterate through the lists.

List<SPUser> thridList = new List<SPUser>();
foreach (SPUser addUser in addUserList) {
  foreach (SPUser listUser in taskListUsers) {
     if (addUser.LoginName != listUser.LoginName) {
         thridList.Add(addUser);
     }
  }
}
That is a lot of looping. Is there a better way to do this? Fortunately, there is. We can run the List<T>.RemoveAll method with a little bit of LINQ logic to essentially create the dual loop thing above.

addUserList.RemoveAll(u => taskListUsers.Any(tu => u.LoginName == tu.LoginName));

Encapsulated in this one line is the entire mess above. To read this we need to know a little about how LINQ works. If it looks confusing, you really need to learn about Lambda Expressions and Anonymous Functions.
For our purposes here, the "=>" means "WHERE", just like in a SQL query. So we are saying that we wan to RemoveAll objects in our list WHERE (=>), the following expression evaluates to TRUE. That's not really what is happening, but for this discussion it works.
From here we want to make sure we go through the entire second list. So we use the List<T>.Any() method. This method works just like a foreach loop, only the method knows that you want to go over all of the individual objects in the list.
The tricky part with the List<T>.Any() method is that it returns a bool. Therefore, what calls this method must be prepared for that. Our code is looking for a "TRUE" evaluation, so we are good to go.

Looking in to the expression inside the List<T>.Any() method, we will return true for any object that has the same SPUser.LoginName as the addUserList object item LoginName. Since it returns true, that particular item will be removed. The RemoveAll() method is already set up for negative logic, UNLIKE our looping example above. Be careful about that... When I psudocoded out the loop solution to figure out how to get the data I wanted, I did get caught by removing all of the items that I didn't want removed.

What we are left with at the end is the addUserList without any of the items who's LoginNames are the same as any of the LoginNames of the items in the taskListUsers.

Phew! A pain to be sure, but when you are using List<T> with complex types, you have to be very sure on how to manipulate your lists to get the correct data out.

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.