Setting multi-valued lookups in forms with jQuery

The solution presented here can be made to work in 2007, 2010 or 2013. I’ll explain later.

Setting SharePoint form fields using jQuery is a pretty standard practice nowadays, and not too hard either. We can fetch the current user, or a value in the query string, and use it to populate form fields, saving our users from having to do it themselves.

Multi-valued lookup columns, however, are not so easy to figure out, and understanding them requires some deep DOM inspection and trial and error.

Consider the form below. It could be an edit form or a new form, it doesn’t matter. They both work in the same way.

screenshot1

Here I have a multi-valued lookup column called “Project Document” with a number of documents in the parent library. The form shows two large text boxes (‘unselected values’ and ‘selected values’), along with an “add” and a “remove” button to shuttle the parent list items between the two text boxes.

Now you’d think that moving the choices from one text box to the other would be enough to “set” the value in the form, but you’d be wrong. Microsoft uses hidden elements to hold the “true” values present on the form, so in order to set that value we have to update both the visible and invisible elements in the form.

Digging into the DOM

The image below shows the DOM representation of the visible elements we are concerned about. This shows a 2013 environment, but the markup is the same in 2010, at least for this part of the form.

screenshot2

What we see here is the two text boxes (rendered as selects) along with options, about 20 or so in the “unselected” textbox, and one, titled “SET READ ONLY DATABASE” in the “selected” text box. Notice the title attributes on these selects, “Project Document possible values” and “Project Document selected values”. We will refer to these as we “move” an option from one select to the other using jQuery.

The second part, the part SharePoint uses under the hood to actually save the lookup values, is a hidden input element located just above the visible UI we have just seen.

screenshot3

There are actually three hidden input controls here, but we only care about the topmost one. It does not have a handy title attribute, but it has an ID of longstringofcrap_MultiLookup. This is the place where 2010 and 2013 are slightly different. The ID is a little different in 2010. We’ll fetch the input using jQuery by using the “ends with” selector, like so:

“[id$=’MultiLookup’]” for 2013 and “[id$=’MultiLookupPicker’]” for 2010.

The hidden input that stores the actual data for the selected items does so in a peculiar, pipe-delimited format. I’ll leave it to you to soak it in and try to make sense of it:

screenshot4

In our function we will need to create that crazy string using the option’s text and value along with that pipe-T thingy, and add or remove it from the input’s value attribute as needed.

Putting it all together

I wanted to write a function to add a selection by name, and another function to remove a selected option, also by name. I also wanted it to be as reusable as possible, so I made the column name and value as parameters to my functions. By including these functions in a globally-referenced JavaScript file, we can use this functionality everywhere on the site. Anyway here’s the code:

//var selector = "[id$='MultiLookupPicker']"; //for 2010 or 2007
var selector = "[id$='MultiLookup']"; //for 2013

function addChoice(text, columnName) {
    $("[title='" + columnName + " possible values'] option").each(function () {
        if ($(this).text() == text) {
            $(this).appendTo($("[title='" + columnName + " selected values']"));
            var multilookupPickerVal = $(selector).val();
            if ($(selector).val() == undefined || $(selector).val().length == 0) {
                $(selector).val($(this).val() + "|t" + $(this).text());
            }
            else {
                $(selector).val(multilookupPickerVal + "|t" + $(this).val() + "|t" + $(this).text());
            }
        }
    });
}

function removeChoice(text, columnName) {
    $("[title='" + columnName + " selected values'] option").each(function () {
        if ($(this).text() == text) {
            $(this).appendTo($("[title='" + columnName + " possible values']"));
            var multilookupPickerVal = $(selector).val();
            var valToRemove = $(this).val() + "|t" + $(this).text();
            var newValue = multilookupPickerVal.replace(valToRemove, "");

            $(selector).val(newValue);
        }
    });
}


A couple of things to point out. I added two variable declarations called “selector”. Make sure only one of them is not commented out – the appropriate one for you environment. For updating the visible text boxes I am using the jQuery appendTo function, a really neat function which removes a DOM element from its current home and places it into the specified location.

Edit: Thanks to jameel’s comment below I fixed an earlier issue where I was hard coding the column name in the function. He also verified the 2010 code works for SharePoint 2007 as well.

Advertisements

The magical IID attribute in SharePoint List Views

SharePoint is full of undocumented and largely unknown hints and tricks you can leverage when you build solutions. Often when I am F12-ing through a site, I come across these little gems and do my best to remember them for future reference.

One such bit that has proven itself useful to me a couple of times is the IID attribute, found on every tr element in every SharePoint List View.

iid screenshot

As you can see, the iid attribute is composed of three comma-separated values. Here’s my best swag as to what these mean:
• Context ID. This is the value of the ctx.ctxId variable in the SharePoint-generated JavaScript included with the List View Web Part.
• Item ID. This is the list item’s ID.
• objectType. This corresponds to the FSObjType, meaning its value will be zero for a list item and one for a folder.

The Item ID is the important value here, at least in my case. I was looking for a way to fetch the ID of the currently selected list item, and the iid attribute, along with the CSS class of s4-itm-selected, gave me what I was looking for. By doing a JavaScript split in the iid attribute and taking the second element of the returned array, I was able to get that ID.

So the jQuery for getting the currently selected list item’s ID is:
var id = $(“tr.s4-itm-selected”).attr(“iid”).split(“,”)[1];

I had to incorporate some checks to make sure there was only one item selected, but this is the jist of the code.

Working with Content Types in the JavaScript Client Object Model

Some things that should be simple are made more difficult in the Client Object Model because of the asynchronous manner in which it works. The Client Object Model does not interact directly with SharePoint’s data – it is really a wrapper for web service calls that send and receive messages from SharePoint. So in order to do anything with the CSOM, you need to send a request to the web service, wait for the response, and then act on it.

A prime case is Content Types. Suppose you had a list with multiple Content Types, and you need to fetch an item and determine its Content Type. You might think you could do something like this:

Var ct = listItem.get_item("Content Type");

Well, there is no such property available in the CSOM object. You could Fiddle the web service response JSON of a simple query like this and you would not see the Content Type’s name anywhere, so there’s no chance the CSOM will be able to get that data. There is, however, and object called ContentTypeId which has a property containing the Content Type’s ID.

In order to get the name of the list item’s Content Type, here’s what we need to do: We need to explicitly query the list’s Content Types collection, load it into the context object, and loop through the collection until we find the one whose ID matches the ContentTypeID we’ve found earlier.

Note: Sometimes it’s not so obvious when we need to call load() on an object when querying the Client Object Model. Here is a rule of thumb I have developed: For “loose” properties, you generally do not need to explicitly load them, but for collections of objects, you need to call load on them. For example, a collection of Content Types on a list, or a collection of Lists in a Web object, must be loaded into the context object explicitly.

The code to do this would look something like this:

//couple of global variables
var listItem = null;
var listContentTypes = null;

function getContentTypeOfCurrentItem(id) {
    var clientContext = new SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getByTitle("Projects");

    //get the list item and load() it
    listItem = oList.getItemById(id);
    clientContext.load(listItem);

    //get the content types and load the collection
    listContentTypes = oList.get_contentTypes();
    clientContext.load(listContentTypes);

    clientContext.executeQueryAsync(Function.createDelegate(this, getContentTypeOfCurrentItemSucceeded), Function.createDelegate(this, onQueryFailed));
}

//the callback
function getContentTypeOfCurrentItemSucceeded(sender, args) {    
    var ctid = listItem.get_item("ContentTypeId").toString();

    var ct_enumerator = listContentTypes.getEnumerator();
    while (ct_enumerator.moveNext()) {
        var ct = ct_enumerator.get_current();

        if (ct.get_id().toString() == ctid) {
            //we've got our content type, now let's get its name
            var contentTypeName = ct.get_name();
        }
    }
}

Setting the Content Type on a new list item

Setting the Content Type on a newly created list item works in about the same manner. You query the collection of Content Types, loop through them to find the one you want, get its ID and pass that ID into the new item using set_item():

var projectList;
function getContentTypeOfCurrentItem(id) {
    var clientContext = new SP.ClientContext.get_current();
    projectList = clientContext.get_web().get_lists().getByTitle("Projects");
    clientContext.load(projectList);

    //get the content types and load the collection
    listContentTypes = oList.get_contentTypes();
    clientContext.load(listContentTypes);

    clientContext.executeQueryAsync(Function.createDelegate(this, getContentTypeOfCurrentItemSucceeded), Function.createDelegate(this, onQueryFailed));
}

function getContentTypeOfCurrentItemSucceeded() {
    var CTID;

    var ct_enumerator = listContentTypes.getEnumerator();
    while (ct_enumerator.moveNext()) {
        var ct = ct_enumerator.get_current();
        if (ct.get_name() == ctName) {
            CTID = ct.get_id();           
        }
    }
   
    var newProjectInfo = new SP.ListItemCreationInformation();
    var newProject = reviewTasksList.addItem(newProjectInfo);
    newProject.set_item('Title', "Foo list item");
    newProject.set_item('ContentTypeId', CTID);
    //set other fields
    newProject.update();
    clientContext.load(newProject);

    clientContext.executeQueryAsync(Function.createDelegate(this, this.SomeCallbackIhaveNotDefined), Function.createDelegate(this, this.queryFailed));
}

Accessing the web property bag with JavaScript

A useful, but largely unknown, feature in SharePoint is the Property Bag. The Property bag is place where site-wide settings can be kept. It is implemented as a collection of name/value pairs. The reason it is largely unknown is that it is not visible from the user interface, nor can it be seen using SharePoint Designer. It can only be accessed by developers, or by administrators using a tool like SharePoint Manager.

In SharePoint 2007, using the Property Bag in an application meant writing C# code, deployed to the server. SharePoint 2010 gives us a couple of options to access the property bag without managed code on the server. These are sandbox solutions and the Client Object Model.

Accessing the Property Bag using the JavaScript Client Object Model is a straightforward process, as the API has this ability built in:

 //wait until client object model dependencies are loaded before executing our code
ExecuteOrDelayUntilScriptLoaded(getWebProperties, "sp.js");

var webProperties;

function getWebProperties() {
    var clientContext = new SP.ClientContext.get_current();
    webProperties = clientContext.get_web().get_allProperties();
    clientContext.load(webProperties);
    clientContext.executeQueryAsync(Function.createDelegate(this, this.getWebPropertiesSucceeded), Function.createDelegate(this, this.onQueryFailed));
}

function getWebPropertiesSucceeded() {

          //debugger; //use this to force a break here
	//returns an object with all properties.  
          //Use the quick watch to expand this out to see all of them.
	var allProps = webProperties.get_fieldValues();

	var customProp = "";

         //make sure the property is there before using it.
	if(webProperties.get_fieldValues().CustomSite_Version != undefined)
	{
		var customProp = webProperties.get_fieldValues().CustomSite_Version;
	}
	alert(customProp);
}

function onQueryFailed(args, sender)
{
     //handle errors here
}

I have not discovered a way to iterate through the web properties like you can with a .Net object, but you can view all of the web properties using the debugger by opening a watch on the webProperties.get_fieldValues() expression:

Here we can see all the site’s properties, including a custom web property I have added: CustomSite_Version.

To fetch a particular property in code, first ensure it is not undefined, then call it much like a .Net-style property:

var customProp = webProperties.get_fieldValues().CustomSite_Version;