From SharePoint Magazine: Variations in Multiselect Controls in Different SharePoint Language Versions

[SharePoint Magazine date=”December 23, 2010″]

Consistency in one’s development practices is a critical part of generating reliable, maintainable code. Most large software contains some inconsistencies, but in your development efforts with SharePoint, you should try your best not to introduce any inconsistencies of your own.

In this article, I’ll show you the effect of one of SharePoint’s little inconsistencies and why it was an issue in my development of SPServices.

If you’ve ever tried to implement jQuery in multi-language SharePoint solutions, you really need to pay attention.

Recently, I had to fix something in SPServices for someone who was using the German version of SharePoint 2010. The issue was with the way some Document Object Model (DOM) elements are identified in the German version. In fact, I’ve run into these inconsistencies before. These inconsistencies can add up to larger bugs not just for people like me who are trying to understand the DOM to make script work, but they probably contribute to issues that arise from installing service packs, hotfixes, and so on.

Whomever did the translations internally to some of the different languages wasn’t consistent in how they made some of the changes to things such as DOM element IDs. The specific case here is with multiselect columns. This inconsistency is the same whether you’re working with SharePoint 2007 or SharePoint 2010.

Anatomy of a Multiselect Column’s Markup

When SharePoint renders the rather complex controls for a multi-select column, it looks something like the image shown here. This is an example from a SharePoint 2010 installation, but it looks basically the same as it does in SharePoint 2007. The control is also the same whether you’re working with a NewForm or an EditForm.

image_thumb4The City column is a multiselect Lookup column. This seemingly simple type of column actually consists of many different DOM elements as well as some fairly complicated underlying JavaScript. (Yes, Virginia, SharePoint uses a lot of JavaScript right out of the box.)

Here is a snapshot of the DOM for the control at a high level:

SNAGHTML4bbbec4_thumbLet’s break that markup down into its components to see how it works:

SNAGHTML4bb3451_thumbBefore we even get to anything visual, there is a set of hidden INPUT elements that the scripts use to manage things. Each of the three INPUT elements have long IDs preceded by [long GUID-based stuff] and ending with the names in the following table:

MultiLookupPicker Used to store the currently selected set of values, using the odd separator ‘|t’, as in ’1|tAbington|t2|tActon|t3|tAcushnet’.
MultiLookupPicker_data Stores the full set of allowable values, like this ’1|tAbington|t |t |t2|tActon|t |t |t3|tAcushnet|t |t ‘ (and so on). It’s not clear why there are extra separators in this one.
MultiLookupPicker_initial Stores the initial value of the column as of page load.

Next comes a table that contains the visual elements of the control:

image_thumb5The left side of the control is a SELECT that has some script attached to its dblclick and change events. The important thing we need to know about this SELECT element is its title. In an English installation, the title=”City possible values”. It’s the column name followed by ‘ possible values’.

<TD class=ms-input>
  <SELECT style="WIDTH: 143px; HEIGHT: 125px; OVERFLOW: scroll" id=ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_SelectCandidate title="City possible values" ondblclick="GipAddSelectedItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" multiple onchange=GipSelectCandidateItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); name=ctl00$m$g_856b69b7_94a3_499a_af52_e1789266b73a$ctl00$ctl05$ctl02$ctl00$ctl00$ctl04$ctl00$ctl00$SelectCandidate>
    <OPTION title=Abington selected value=1>Abington</OPTION>
    <OPTION title=Acton value=2>Acton</OPTION>
    <OPTION title=Acushnet value=3>Acushnet</OPTION>
    <OPTION title=Wrentham value=350>Wrentham</OPTION>
    <OPTION title=Yarmouth value=351>Yarmouth</OPTION>

Next comes a 10-pixel-wide spacer TD. It’s not very interesting, but I mention it for completeness. Usually you’d probably do spacing like this in the CSS rather than having a dedicated TD for it.

<td style="padding-left: 10px;"/>


Next come the two buttons. Both buttons have some script attached to their click event that manages the movement of the values between the left and right boxes.

<TD class=ms-input vAlign=center align=middle>
  <BUTTON id=ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_AddButton class=ms-ButtonHeightWidth onclick="GipAddSelectedItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" type=submit>Add &gt;</BUTTON>
  <BUTTON id=ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_RemoveButton class=ms-ButtonHeightWidth disabled onclick="GipRemoveSelectedItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" type=submit>&lt; Remove</BUTTON>


Now another 10-pixel-wide spacer TD:


And finally, we have the right side box. Like the left side, this is also a SELECT with some script attached to its dblclick and change events. The important thing we need to know about this SELECT element to work with it is its title. In an English installation, title=”City selected values”. It’s the column name followed by ‘ selected values’.

<TD class=ms-input>
  <SELECT style="WIDTH: 143px; HEIGHT: 125px; OVERFLOW: scroll" id=ctl00_m_g_517395ec_085b_47db_b888_b55773e76d86_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_SelectResult title="City selected values" ondblclick="GipRemoveSelectedItems(ctl00_m_g_517395ec_085b_47db_b888_b55773e76d86_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" multiple onchange=GipSelectResultItems(ctl00_m_g_517395ec_085b_47db_b888_b55773e76d86_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); name=ctl00$m$g_517395ec_085b_47db_b888_b55773e76d86$ctl00$ctl05$ctl02$ctl00$ctl00$ctl04$ctl00$ctl00$SelectResult>
    <OPTION title=Abington value=1>Abington</OPTION>
    <OPTION title=Acton value=2>Acton</OPTION>
    <OPTION title=Acushnet value=3>Acushnet</OPTION>


There’s a lot more to this simple little thing that you might have expected, eh? And I haven’t even gone into how the underlying script works. In fact, that’s so complicated that I’m going to leave it for another time.

The Inconsistency

But back to the whole point of this article, which is the inconsistency across language versions of SharePoint. The key is to be able to find the important piece parts of the control in the DOM with SPServices (in my case) and be sure that we are working with the right one if there are multiple multiselect columns in the form. To do this, I have a function in SPServices called dropdownCtl. Its little purpose in life is to find a “dropdown” and return an object reference to it in the DOM as well as its “Type”. The functions SPCascadeDropdowns, SPDisplayRelatedInfo, and SPLookupAddNew in SPServices all use the dropdownCtl function to find the right elements in the DOM to work with.

One other tricky part is that the are three types of “dropdowns” we can have in the form. (I’m using “dropdowns” in quotes because it’s a little bit of a stretch to call the multiselect control a dropdown.) Each type has different representations in the DOM elements, so dropdownCtl needs to find the right element and also decide which type of element it is.

The first two types are what you usually would think about as dropdowns, and they are easier to find in the DOM. They are either a “simple SELECT,” which is rendered if there are fewer than 20 options for the column, or a “complex SELECT,” which SharePoint uses if there are 20 or more options. The “complex SELECT” is actually a hybrid INPUT/SELECT with some script to drive it.

In the case of a “multiselect dropdown,” it’s a little trickier. The best way to find the various elements in the page for a “multiselect dropdown” is to look for the SELECTs’ titles. This is where the translation differences come into play.

It’s understandable (perhaps) that the English titles should be translated into the other languages. What doesn’t make sense is that the format of those translations is quite different. Let’s look at what the City column’s right box would have as its title in three different languages:

English City selected values
Russian Выбранных значений: City
(this is for the possible values, but the point is the same)
German Ausgewählte Werte für “City”

As you can see, the pattern of the text varies, not just the words themselves. I don’t speak Russian or German, but it’s hard to imagine that it’s necessary to change the construct from ‘City selected values’ to ‘Selected values: City’ or ‘Selected values for “City”‘ in order to reflect some sort of nationalistic thinking.

In my code, I don’t want to care about what language is being used; I just want to be able to find the right title. By using different patterns, SharePoint makes this more difficult than it should be.

The Solution

Because of the differences between the titles in the different language versions of SharePoint, in my dropdownCtl function I have to have these three conditions:

// Multi-select: This will find the multi-select column control in English and most other languages sites where the Title looks like 'Column Name possible values'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title^='" + colName + " ']")).html() != null) {
  this.Type = "M";
  // Multi-select: This will find the multi-select column control on a Russian site (and perhaps others) where the Title looks like 'Выбранных значений: Column Name'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=': " + colName + "']")).html() != null) {
  this.Type = "M";
  // Multi-select: This will find the multi-select column control on a German site (and perhaps others) where the Title looks like 'Mögliche Werte für &amp;quot;Tops&amp;quot;.'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$='\"" + colName + "\".']")).html() != null) {
  this.Type = "M";

Obviously, I can handle this in my code, but every time I hear from someone who uses a different language version of SharePoint and sees a bug, I need to revisit what the pattern of the text is in that language.

The Moral of the Story

The moral of the story is that seemingly tiny decisions can actually make more significant differences. Consider very carefully why you want to change the style of something like an ID or markup that you’re rendering in a control because there can be ramifications later. We all want to leave a little bit of our personal style in our code, but try not to do so at the expense of reusability, interoperability, or consistency.


Since I wrote this article back in 2010, I’ve made adjustments to handle additional languages. I’ve added Italian and I’ve recently been asked for a fix for Portuguese. Should you find another inconsistency, please let me know by posting in the SPServices Discussions on Codeplex.

Here’s what my DropdownCtl function looks like in version 2013.01:

// Find a dropdown (or multi-select) in the DOM. Returns the dropdown onject and its type:
// S = Simple (select);C = Compound (input + select hybrid);M = Multi-select (select hybrid)
function DropdownCtl(colName) {
  // Simple
  if ((this.Obj = $("select[Title='" + colName + "']")).length === 1) {
    this.Type = "S";
    // Compound
  } else if ((this.Obj = $("input[Title='" + colName + "']")).length === 1) {
    this.Type = "C";
    // Multi-select: This will find the multi-select column control in English and most other languages sites where the Title looks like 'Column Name possible values'
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title^='" + colName + " ']")).length === 1) {
    this.Type = "M";
    // Multi-select: This will find the multi-select column control on a Russian site (and perhaps others) where the Title looks like 'Выбранных значений: Column Name'
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=': " + colName + "']")).length === 1) {
    this.Type = "M";
    // Multi-select: This will find the multi-select column control on a German site (and perhaps others) where the Title looks like 'Mögliche Werte für &quot;Column name&quot;.'
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$='\"" + colName + "\".']")).length === 1) {
    this.Type = "M";
    // Multi-select: This will find the multi-select column control on a Italian site (and perhaps others) where the Title looks like "Valori possibili Column name"
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=' " + colName + "']")).length === 1) {
    this.Type = "M";
  } else {
    this.Type = null;
} // End of function DropdownCtl

jQuery library for SharePoint Web Services v0.2.6 Alpha

UPDATE: 2009-08-28: This library doesn’t sit still for long.  We’ve released version 0.2.7 to fix a small issue and are already working on version 0.2.8.

I just posted a new alpha version of our jQuery library for SharePoint Web Services.  In it, we’ve added 5 new operations, and a new function: SPServices.SPCascadeDropdowns.

Web Service Operation Options MSDN Documentation
Lists GetAttachmentCollection [webURL], listname, ID Lists.GetAttachmentCollection Method
Versions DeleteAllVersions fileName Webs.DeleteAllVersions Method
  DeleteVersion fileName, fileVersion Webs.DeleteVersion Method
  GetVersions fileName Webs.GetVersions Method
  RestoreVersion fileName, fileVersion Webs.RestoreVersion Method

The SPCascadeDropdowns function is a translation of the cascading dropdowns JavaScript that I’ve posted about here and here.  Since those two posts are two of my most popular, I wanted to convert that logic to jQuery.  Even nicer, because we now have the jQuery library working with Web Services, we were able to put in calls to read from reference lists, removing the requirement for a hidden DVWP on the page.

Right now, the function works only if there are fewer than 20 options in the dropdowns.  As many of you know, for some reason SharePoint changes the control it uses if there are 20 or more options.  We’ll get that logic into the function soon, but we wanted to get this out there for folks to bang on.

Since it’s an alpha, we’re looking for some “swarm testing”.  Anything you can do to try it out would be appreciated.  Let us know if you have any problems in the Codeplex Issue Tracker or offer up additional thoughts in the Discussions.

By the time you read this, we might be outta alpha, but thanks if you can help!

Cascading Dropdown Columns in a SharePoint Form – Part 2

UPDATE 2009-08-26: We’ve translated this logic into our jQuery Library for SharePoint Web Services. I *strongly* suggest that you look at that as an option, as it is far more robust.  And free!

In my post last week entitled Cascading Dropdown Columns in a SharePoint Form – Part 1, I showed how to create cascading dropdowns, meaning that a choice in the first dropdown would change the options in the second dropdown.  The example I gave was from a client project which had over 20 options in the second dropdown.  Because of this, the required JavaScript was a bit complicated because of the way that SharePoint renders the control for more than 20 options.

I got some great questions about that post and I promised to post again with a simpler example with a working demo. I’ve created a new Demo site with a CascadingDropdowns demo page on our Sympraxis Consulting Web site (all WSS!).

Notes to the critics:  Sure, you could read the lists directly from the JavaScript.  But this method seems like it will be easier to follow for the average “middle tier” developer (or non-developer).  It’s also possible to simply embed the table of value relationships directly in the page (hard-wired), though I wouldn’t recommend it.  (Use lists for what they are good for!)

Hopefully the page will be self-explanatory, but since this is a new way to demo my code, I appreciate any feedback you might have about how to make it more useful.

Looking at the page at the above link should give you all of the details, but the JavaScript looks like this:


var Column1 = new Object();
var Column2 = new Object();
var savedColumn2Options = new Object();

function SetUpCascading() {
    // Find Column1 in the DOM (in this demo, the column is named "Region")
    Column1 = getTagFromIdentifierAndTitle("select","","Region");
    // Find Column2 in the DOM (in this demo, the column is named "State")
    Column2 = getTagFromIdentifierAndTitle("select","","State");
    // Attach the onchange event to Column1
    Column1.attachEvent('onchange', Column1Changed);
    // Call the onchange event to set the initial options for Column2

function Column1Changed() {
    // Find the table with the Column1 / Column2 / ID information
    var Column1Table = document.getElementById("Column1Table");
    // Find all of the table rows
    var Column1TableRows = Column1Table.getElementsByTagName("TR");
    // See which Column2 options are allowed for the chosen Column1 option
    Column2Count = 0;
    // For each of the table rows...
    for (var i=0; i < Column1TableRows.length; i++) {
        // Get the table detail cells
        var Column1TableRowsDetails = Column1TableRows[i].getElementsByTagName("TD");
        // If the Region value in the table row matches the currently chosen Region
        if(Column1TableRowsDetails[1].innerHTML == Column1.options[Column1.selectedIndex].text) {
            // Add the option to the Column2 dropdown
            Column2.options[Column2Count] = new Option(Column1TableRowsDetails[0].innerHTML,
            // Increase the count of available options for Column2
            // For this demo, set the background color of the Column2 cell to green
            Column1TableRowsDetails[0].style.backgroundColor = "green";
        // If the Region value in the table row doesn't match the currently chosen Region
        } else {
            // For this demo, set the background color of the Column2 cell to red
            Column1TableRowsDetails[0].style.backgroundColor = "red";
    // Set the length of the options array
    Column2.options.length = Column2Count;
    // If there aren't any available choices, then disable the Column2 dropdown
    if(Column2Count == 0) Column2.disabled = true;
     else Column2.disabled = false;

and the key templates in the DVWP look like this.  Note that the table must have a unique id, which is used by the JavaScript to find it in the page: table id="Column1Table".

<xsl:template name="dvt_1">
    <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
<table id="Column1Table" border="0" width="100%" cellpadding="2" cellspacing="0">
        <xsl:for-each select="$Rows">
            <xsl:sort select="@Title" order="ascending"/>
            <xsl:call-template name="dvt_1.rowview"/>

<xsl:template name="dvt_1.rowview">
<td class="ms-vb">
            <xsl:value-of select="@Title"/>
<td class="ms-vb">
            <xsl:value-of select="@Region"/>
<td class="ms-vb">
            <xsl:value-of select="@ID"/>

Cascading Dropdown Columns in a SharePoint Form – Part 1

UPDATE 2009-08-26: I’ve translated this logic into my jQuery Library for SharePoint Web Services. I *strongly* suggest that you look at that as an option, as it is far more robust.  And free!

UPDATE 2009-07-19: I’ve added a second post on this with a simpler example. It also contains a link to a demo page which shows how it all fits together.

Over in the MSDN SharePoint – Design and Customization Forum there are frequent questions about “cascading” dropdowns.  This means having two (or more) dropdowns “connected” based on the choices the user makes.  I dug out an example from a client project that demonstrates this technique fairly well so that I could refer folks to it.

In this example, the client had Divisions, each with a set of Branches.  We stored the Division / Branch pairs in a simple SharePoint Custom List in the root site of the Site Collection which had just those two columns.  (We actually used the Title column in the list for the Branch just to keep it really simple.  This list wasn’t one that anyone outside of the administrators was going to be looking at.)

We wanted to have the cascading work in the forms for a Document Library where each document would be categorized as belonging to a Division and Branch.  (The Division and Branch columns were both Lookup columns into lists at the Site Collection root.)  To make the Division and Branch columns cascade in the forms, we needed to have these pieces in place:

  • A hidden Data View Web Part (DVWP) which put the Division / Branch pairs into a table on the form for use by the
  • JavaScript to change the available values for Branch once the Division was selected.

We didn’t actually need to customize the form itself; we left the default List Form Web Part (LFWP).  The major reason for this was that we had upward of 30 Content Types that could be stored in the Document Library, and we wanted the Content Type selection to work the clean, default way.  All of the changes we made to the form were done with the client-side JavaScript.

Here are the main XSL templates from the hidden DVWP.  Note that there is nothing fancy about this DVWP except that I gave the table an id=”BranchesTable” so that I could locate it in the DOM with the JavaScript:

<xsl:template name="dvt_1">
    <xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row"/>
<table id="DivisionTable">
    <xsl:for-each select="$Rows">
        <xsl:sort select="@Branch" order="ascending"/>
        <xsl:call-template name="dvt_1.rowview" />

<xsl:template name="dvt_1.rowview">
      <xsl:value-of select="@Division"/></td>
      <xsl:value-of select="@Title"/>
      <xsl:value-of select="@ID"/>

Next was the JavaScript.  As I always do, I stored the JavaScript in a Document Library in the root site of the Site Collection called JavaScript.  This way I could reuse it anywhere I needed to.  There were a lot of other things I was doing in the JavaScript to make the form work more richly, but I’ve snipped out the pieces that are relevant to this specific task (hopefully not breaking anything in so doing!).

First, I needed to attach an onchange event to the Division column’s dropdown.  This code snippet was called as part of the form load with _spBodyOnLoadFunctionNames.push();

// Find the Branch column
var Branches = getTagFromIdentifierAndTitle("input","","Branch");
// Save the Branch choices so that we can rebuild as needed
var savedBranchesChoices = Branches.choices + "|";
// Find the Division column
Division = getTagFromIdentifierAndTitle("select","","Division");
// Attach the onchange event
Division.attachEvent('onchange', DivisionChanged);

Second, here’s the JavaScript to handle the onchange event.  A few notes that are important here:

  • This was an environment where Internet Explorer 6+ was the supported browser.  We didn’t need to worry about any other browsers working well with the JavaScript (though I don’t think I was doing anything that wouldn’t work in other browsers).
  • There were more than 20 Branches.  When this is the case, SharePoint generates the dropdown control for the column differently than if there are fewer than 20 values.  I won’t got into the details of this; suffice it to say that some of the peculiarities below are there to manage this difference.
function DivisionChanged() {
    var chosenDivision = Division.options[Division.selectedIndex].text;

    // Find the hidden table with the Division / Branch / ID information
    var DivisionTable = document.getElementById("DivisionTable");
    var DivisionsTableRows = DivisionsTable.getElementsByTagName("TR");
    BranchesChoices = savedBranchesChoices;
    // See which choices ought to remain for this Division
    var newChoicesList = '';
    while(BranchesChoices.length > 0) {
        // Grab the first choice in the list
        var thisBranch = BranchesChoices.substring(0, BranchesChoices.indexOf("|"));
        // Trim the first choice out of the choices
        BranchesChoices = BranchesChoices.substring(thisBranch.length + 1);
        // Grab this choice's index
        var thisBranchIndex = BranchesChoices.substring(0, BranchesChoices.indexOf("|"));
        // Trim the first index out of the choices
        BranchesChoices = BranchesChoices.substring(thisBranchIndex.length + 1);
        // See if this Branch is in the table for this Division
        for (var j=0; j < DivisionsTableRows.length; j++) {
            var DivisionsTableRowsDetails = DivisionsTableRows[j].getElementsByTagName("TD");
            if(DivisionsTableRowsDetails[0].innerHTML == chosenDivision &&
                    DivisionsTableRowsDetails[2].innerHTML == thisBranchIndex)
                newChoicesList = newChoicesList + thisBranch + "|" + thisBranchIndex + "|";
    // The choices now become the filtered list, allowing for no selection: (None)
    if(newChoicesList.length > 0)
        Branches.choices = "(None)|0|" + newChoicesList.substring(0, newChoicesList.length - 1);
        Branches.choices = "(None)|0";        
    Branches.value = "";