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

Declaratively provisioning a lookup column

In SharePoint development, developers are often stymied be seemingly simple tasks. Provisioning a lookup field using the declarative model in a solution package is one such task. The most common symptom of an improperly-provisioned lookup is that no error will occur on deployment, but the lookup field will have an empty reference to its parent list when viewed in List Settings. Developers will usually, and correctly, assume that the field is broken because the parent list did not exist yet when the field is provisioned. The most common way I have seen developers address this is to use a Feature Receiver to provision their lookups after declaratively provisioning everything else because they cannot figure out a way to make the declarative approach work.

Today on Stack Exchange I saw another question on this topic, and I felt compelled to write this post to clarify the issue.

The trick to doing this right is understanding that the order in which the package deploys the artifacts is important. Look at the image below, which shows a fairly representative simple data model for a SharePoint solution. The screen shot is from VS2102/SP2013, but it works exactly the same in 2010.

package

It shows fields, content types, and lists, deployed in that order. Now, the Projects list has a lookup to the Clients list. If I put that lookup in the SiteColumns element, it will fail, because it will have been provisioned in the wrong order. The practice I have developed to make this work is to put the Field element for the lookup directly under the ListInstance element it depends on. By doing this there is no possibility the lookup will be provisioned out of order, because SharePoint will deploy the stuff in an individual elements file in the order it appears in the file:

elements

Now, when I provision my related lists, everything is properly hooked up, and I didn’t have to write any code to do it.

…and by the way, I discovered this trick by taking a saved site template and opening it up using the “Import a Solution Package” project type in Visual Studio 2010 (you can learn a LOT about SharePoint’s provisioning process by doing this, by the way). And this approach works exactly the same in 2010 and 2013 (and, I would suspect, in 2007 as well).

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;

Adding Rounded Corners to Top Navigation

Update: This post contains advice I don’t recommend anymore. Changing the meta tag value to “IE=9” is known to cause some problems with SharePoint 2010. For starters, I’ve noticed that trying to save a list item with a multi-line text field will result in an error (something about an xsd namespace not defined). I have also heard it causes some issues with People Pickers. /Update

Recently I had a requirement to create a custom look and feel on a SharePoint master page. Among the requirements was to implement rounded corners in the Top Navigation bar. Not being a branding guy I really didn’t know how I was going to implement this. I figured the solution would involve images and lots of meticulous tweaking of the DOM to find a combination that worked.

When I asked about the minumum browser version and was told it would be IE9 I felt there had to be a better way to do this using HTML5 and CSS3.

Turns out rounded corners are an out-of-the-box feature in IE9 and all the latest browsers. Implementing them in SharePoint was actually a simple proposition, once a couple of hurdles are cleared.

Implementing this solution involved making some changes to the site’s master page – a couple of CSS classes and a meta tag in the page’s head. The first step was to find the DOM representation of the top navigation bar. In my environment it looked something like this:

The top nav elements are composed of an unordered list, and each list item has a class of “static”. Inside each list item is an anchor, also with a class of “static”. In addition, the currently selected nav item has the extra class “selected”. These are the DOM elements we have to work with.

In all I want to define three CSS classes: one for the default look, one for the selected item, and a third to capture the “hover” event.

To implement the rounded corners we’ll use the new CSS property “border-radius”. There are quite a few options to specify with this property, but I’m going to use the simplest syntax, which just specifies a number to use with all four corners. I also needed to add a bit of a margin on the right to keep some separation between the nav elements. I also had an annoying light blue border around the anchor, so I modified that property as well. I also discovered a CSS3 class for adding drop shadows to the nav elements(why? Because I can!). When all was said and done the default style looks like this:

.s4-tn ul li.static a.static
{
	border-color: transparent;
	background-color: #ccccee;
	border-radius: 10px;
	margin-right:10px;	
	box-shadow: 6px 1px 5px rgba(150, 150, 150, 0.5);
}

To add the selected and hover versions of the style, we’ll use all the properties in the default style except for “background-color”. So our new styles will simply replace this one property, like this:

.s4-tn ul li.static a.static.selected
{
	background-color: #8888cc;		
}

.s4-tn ul li.static a.static:hover
{
	background-color: #dddd99;
}	

When we save and open the site, we’ll notice the rounded corners if we use Chrome or Firefox, but not in IE9. To make this work in IE9, we have to modify this meta tag in the master page:

<meta http-equiv="X-UA-Compatible" content="IE=8"/>

…and change the value to “IE=9”.

Now we have round-cornered navigation elements, as well as custom styles for selected and hover scenarios.

Note…The Publishing Infrastructure feature alters the way the DOM is constructed for the Top Navigation. I tested this against both versions, but if you find any anomalies, let me know.