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

27 thoughts on “Setting multi-valued lookups in forms with jQuery

  1. Pingback: Sharepoint lookup add insert field for multi select lookup column | Edward de Leau

  2. Tried this out and it works for Sharepoint 2007 as well (with the 2010 selector style).
    Thanks for setting this up so cleanly, though I was not sure why the ‘Project Document’ title was hard-coded since it would be the same as the column parameter.

    • ahh, yes, you are right, I neglected to properly replace “Project Documents” with the parameter name passed to the function. Good catch, and thanks for pointing it out. I’ll replace it, and add your comment that it works in 2007, when I get the chance.

  3. Hm… this only seems to work if there’s only 1 multi select on the page. If there are multiple multiselects on the page, it sets all of them. I’ll be toiling away on a fix, and will let you know if I come up with one.

    • You’ll need to refine your selector a bit to catch a specific multi-select box. In SharePoint 2013 there is a element which has the column’s display name inside it. You’ll have to fetch that, and then traverse up the DOM using parent() to find the container that holds the multi select. In 2010 there was an attribute on the tr element, so it was easier to fetch. If you come up with a solution let me know, and I’ll update the post. Good catch, and thanks!

  4. I think this works:

    function addChoice(text, columnName) {

    // let’s get the id from the column name
    var thisID = $(“[title='” + columnName + ” possible values’]”).prop(‘id’);
    thisID = thisID.substr(0, thisID.indexOf(‘_SelectCandidate’));
    selector = “[id$=” + thisID + “_MultiLookupPicker]”;
    $(“[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());
    }
    }
    });
    }

  5. Also, I’m using this to set defaults based on a record pulled via spservices getlistitems(). Getlistitems returns a string in the format of:
    1;#Ashland;2;#Adams;8;#Dane

    So, I wrote a little helper function to make it easier to pass the values in to your function, like so:
    function setMultiSelectValues(inString, column) {
    var outString = inString.split(“;#”);

    $.each(outString, function( index, value ) {

    if (isNaN(value)) {

    addChoice(value, column);
    }
    });

    }

    // Call it like so, assuming you’ve used spservices to pull the data to begin with
    // get and set Home Office
    collaboratorsHomeOffice = $(this).attr(“ows_CollaboratorsHomeOffice”);
    setMultiSelectValues(collaboratorsHomeOffice, ‘Collaborators Home Office’);

    Just thought I’d offer that up in case anyone else is using it for similar purposes. Thanks for your work!

  6. Thank you for this code snippet – it saved me a lot of headaches after our old webparts’ jquery broke in 2013. However, there is one small change required in the first line of the removeChoice method – assuming it’s a copy/paste typo:

    $(“[title='” + columnName + ” possible values’] option”).each(function () {
    should be:
    $(“[title='” + columnName + ” selected values’] option”).each(function () {

    Otherwise, when you’re trying to remove the value, it’s looking in the possible values listbox – which is not where you’re trying to find the item.

  7. Hey there,
    After further playing around, I found some wonkiness in the removeChoice function. Basically, it wasn’t cleanly removing everything, depending on the order of checking/unchecking, and would throw an “out of bounds” error. I _think_ this rewrite works:

    function removeChoice(text, columnName) {
    // let's get the id from the column name
    var thisID = $("[title='" + columnName + " selected values']").prop('id');
    thisID = thisID.substr(0, thisID.indexOf('_SelectResult'));
    selector = "[id$=" + thisID + "_MultiLookupPicker]";

    // loop through the selected options
    $("[title='" + columnName + " selected values'] option").each(function () {

    if ($(this).text() == text) {
    $(this).appendTo($("[title='" + columnName + " possible values']"));

    var multilookupPickerVal = $(selector).val();

    // call the internal function which creates a clean array out of the weird string
    var splitValue = GipSplit(multilookupPickerVal);

    // set the 2 array nodes we want to remove
    var valToRemove = $(this).val();
    var textToRemove = $(this).text();

    // Kill the value and text in the array
    splitValue = jQuery.grep(splitValue, function(value) {
    return value != valToRemove;
    });

    splitValue = jQuery.grep(splitValue, function(value) {
    return value != textToRemove;
    });

    var newValue = '';

    // loop through the cleaned up array and rebuild the selector value
    for (var i = 0; i < splitValue.length; i++) {
    if (newValue.length == 0) {
    newValue = splitValue[i];
    }
    else {
    newValue = newValue + '|t' + splitValue[i];

    }

    } // end for loop

    // set the new value to the selector
    $(selector).val(newValue);

    }
    });
    }

      • SharePoint 2010, and what I’m doing is hooking up another function so that I can have a checkbox UI instead of the multi-select box ui. The multi-select UI is there, but hidden. So, when a user unchecks a checkbox, I’m calling removeChoice(). If I do a bunch of checking/unchecking, with your code, I wind up with some errant |t bits hanging around – sometimes at the end of the string, sometimes next to one that’s supposed to be there. I’m using jQuery 1.8.3, because of some other dependencies. So, it’s possible that it’s a weirdness in that version of jQuery’s replace() function…. I can send you the checkbox functions if you want. (I’m planning to do a blog post about it soon.)

  8. OK – so when you do a bunch of selecting and unselecting, the collection is probably getting messed up and the indexes get out of sync. You might end up needing to re-fetch the collections somehow. If you do end up sorting it out and writing that blog post please come back and link to it here, I’d like to see it.
    I’m curious to look into this myself, but it will be awhile before I’m able to.

    • One thing you might try is to hold off on updating the actual hidden SharePoint controls until the user hits the Save button. That way you are not shuffling things around and potentially messing up those elements. You could look into the PreSaveAction() function to accomplish this.

      • I’ve got it working with the removechoice code I posted above. I’ll see if I can get the blog post done today. I need to document this before I forget what the heck I’m doing anyway. 🙂

  9. Pingback: Replacing Multi-select Lookup field with Checkboxes | Blah-de-blah-blog

  10. I have successfully used code from this topic, so thank you for that. However, I now have a need to determine which item(s) are highlighted in either the LH or RH list. The form must know this because that is how the scripts associated with the Add or Remove buttons know which item(s) to act on. I just can figure out how to glean that attribute via javascript.

  11. thanks for this post, but when i removed an item from the selected options i got an jquery error which manly cause there is no any option selected the error apear in line of code:

    “master.resultControl.options[pos].selected = true;”

    with message:
    “Uncaught TypeError: Cannot set property ‘selected’ of undefined”

  12. Apologies if my question is a bit simple but where do you call these functions? On the click event of the ‘add’ and ‘remove’ buttons?

    • Hi Ben, how you use the functions is totally up to you. I think the most common use case for this would be to pre-load the desired values on page load based on a query string value or some other metadata.

      • Thanks for your reply. I found your post while having problems with the multi-select list not saving the selected records in the underlying list but I managed to work that out myself. Coincidentally, I’ll need to use your solution in the near future though so thanks in advance!

  13. Hi, thank you for this solution. I just want to point out that even if you are using SharePoint 2013 it is still possible that the correct ID is ’MultiLookupPicker’. We have custom forms and when we used ‘MultiLookup’ the selected values field got filled but when saved there were no values. So I checked and found out that the id for the hidden input field is ’MultiLookupPicker’ and not ‘MultiLookup’. We have SharePoint 2013 on premise and it was upgraded from 2010 (site collection is also upgraded), so maybe thats the catch.

    • Hi, good catch, and thanks for letting me know. Yeah, it’s most likely that since the site originated in 2010 it retained the 2010 page structure. There are so many permutations of SharePoint it’s impossible to test everything.

  14. Here is a different/little easier method:

    Remove item:

    function removeItem(fieldTitle, val) {

    $(“select[title='” + fieldTitle + ” selected values’] option”).removeAttr(‘selected’);

    $(“select[title='” + fieldTitle + ” selected values’] option[value='” + val + “‘]”).prop(‘selected’, true).parents(‘tr’).find(“[id$=’_RemoveButton’]”).click();

    }

    Add item:

    function addItem(fieldTitle, val) {

    $(“select[title='” + fieldTitle + ” possible values’] option”).removeAttr(‘selected’);

    $(“select[title='” + fieldTitle + ” possible values’] option[value='” + val + “‘]”).prop(‘selected’, true).parents(‘tr’).find(“[id$=’_AddButton’]”).click();

    }

    This has been verified w/ 2013.

  15. Very useful!, however to use ‘title’ as selector is not the best as it won’t work in other languages for example, in Norwegian…
    “Mulige verdier for XXXXX”

    Also there’s the chance that there are other MultiLookup fields, so I made another function based on yours.

    I hope someone finds it useful.

    function SelectChoiceByValue(fieldName,value){

    var selector = $(“input[id*='”+fieldName+”_’][id$=’MultiLookup’]”);

    $(“table[id*='”+fieldName+”_’] select[id*=’_SelectCandidate’] option”).each(function () {

    if ($(this).val() == value) {

    var selectedResults = $(“table[id*='”+fieldName+”_’] select[id*=’_SelectResult’]”);

    $(this).appendTo(selectedResults);
    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());
    }
    }

    });

    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s