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.
The 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:
Let’s break that markup down into its components to see how it works:
Before 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:
The 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> </SELECT> </TD>
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 ></BUTTON> <BR> <BR> <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>< Remove</BUTTON> </TD>
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> </SELECT> </TD>
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 &quot;Tops&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.
Postscript
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 "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 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