A jQuery Library for SharePoint Web Services (WSS 3.0 and MOSS): The SPSetMultiSelectSizes Function

Cross-posted from EndUserSharePoint.com

SPSetMultiSelectSizes is a function in the jQuery Library for SharePoint Web Services that lets you set the sizes of multi-select picker boxes based on the values they contain. This may sound trivial, but because of the way SharePoint constructs the pickers as compound controls, it’s less straightforward than you might think.

Background

In an old post on my blog, I sketched out how you could set go about Setting Multi-Select Widths in a SharePoint EditForm.aspx Using JavaScript.  The code I posted worked just fine, but took some manual tweaking every time you wanted to use it.  The basic idea is this: When SharePoint renders the multi-select control, the <div>s which contain the selects have a fixed width of 143px.  (Who knows where *that* number came from?!?!)  This is all well and good in the example below.  All of the states fit well inside the <div>s and are quite legible.


But what about the case where the values in your multi-select column are much longer? In the example below, I’m showing a multi-select column which takes its values from the list of Web Services operations which the jQuery Library for SharePoint Web Services currently supports. (Yes, I keep track of things in SharePoint lists.  Isn’t that wonderfully recursive?)  As you can see, it would be very easy to choose the wrong value from among those which start with “GetGroupCollection” unless you scroll to the right, which feels cumbersome at best.


So what I wanted to do was to build a function into the library to deal with this better, and now I have SPSetMultiSelectSizes in v0.4.8ALPHA1. Sure, it’s an alpha, but it works; give it a whirl.

This function led me on several wild goose chases. At first, I couldn’t for the life of me think of how I could reliably figure out what the right width should be. I wrote a post on my blog (Setting Multi-Select Picker Widths in a SharePoint Form Reliably) basically saying “Help!”. To make the function work regardless of the fontSize, fontFamily, fontWeight, etc., that a site may be using, I knew that I couldn’t take any shortcuts. After going back and forth with the ideas I had in that post, plus several others, it finally occurred to me that I could take advantage of the jQuery clone() function for this. What I really needed to do was to clone the existing select, tweak a few of the clone’s CSS attributes, and then use it to determine the best width for the multi-select picker boxes.

Here’s what I’ve come up with for v0.4.8ALPHA1. I expect that it will evolve a bit as it gets some real use, but the fundamental logic is sound.

$.fn.SPServices.SPSetMultiSelectSizes = function (options) {
      var opt = $.extend({}, {
            multiSelectColumn: ""
      }, options);
      // Create a temporary clone of the select to use to determine the appropriate width settings.
      // We'll append it to the end of the enclosing span.
      var possibleValues = $().find("select:[Title='" + opt.multiSelectColumn + " possible values']");
      var selectedValues = possibleValues.closest("span").find("select:[Title*=' selected values']");
      var cloneId = "SPSetMultiSelectSizes_" + encodeColumn(opt.multiSelectColumn);
      possibleValues.clone().appendTo(possibleValues.closest("span")).css({
            "width": "auto",        // We want the clone to resize its width based n the contents
            "height": 0,                  // Just to keep the page clean while we are using the clone
            "visibility": "hidden"  // And let's keep it hidden
      }).attr({
            id: cloneId,                  // We don't want the clone to have the same id as its source
            length: 0                     // And let's start with no options
      });
      var cloneObj = $("#" + cloneId);
      // Add all the values to the cloned select.  First the left (possible values) select...
      possibleValues.find("option").each(function() {
            cloneObj.append("<option value='" + $(this).html() + "'>" + $(this).html() + "</option>");
      });
      // ...then the right (selected values) select (in case some values have already been selected)
      selectedValues.find("option").each(function() {
            cloneObj.append("<option value='" + $(this).html() + "'>" + $(this).html() + "</option>");
      });
      // We'll add 5px for a little padding on the right.
      var divWidth = $("#" + cloneId).width() + 5;
      // Subtract 17 from divWidth to allow for the scrollbar     for the select   
      var selectWidth = divWidth - 17;
      // Set the new widths
      possibleValues.css("width", selectWidth + "px").parent().css("width", divWidth + "px");
      selectedValues.css("width", selectWidth + "px").parent().css("width", divWidth + "px");

      // Remove the select's clone, since we're done with it
      $("#" + cloneId).remove();
};

If you don’t want to understand the details of the jQuery above, here it is in pseudocode.

  • Based on the column’s DisplayName on the form, find the two selects in the boxes in the multi-select picker
  • Clone the select in the left box (“possible values”) and append it to the <span> which encloses the compound control
  • Change the CSS on the cloned select so that its width automatically adjusts based on its contents (this is what we’re used to selects doing most often)
  • Load all of the possible values into the clone. We need to grab the values from both the left (“possible values”) select and the right (“selected values”) select. By taking this approach, the logic works whenever it might be applied.
  • See what the width of the clone is and make a couple of small adjustments for the scroll bar and adding a little right side padding
  • Set the widths of the two boxes (actually a select wrapped in a div for each)
  • Remove the clone from the DOM, since we’re done with it

So, what does all of this work that I went through actually get you? Well, it may not seem like much, but it’s another arrow in your quiver to use in the battle against GIGO.

Example

Going back to the example above, we’d like to have the multi-select boxes in the “Web Service Operations” multi-select picker set just wide enough so that we can see all of the values without scrolling.

Here’s what the picker looks like “out of the box”…

…and after making the call to SPSetMultiSelectSizes:

Syntax

As with all of the functions in the jQuery Library for SharePoint Web Services, everything is put in place from the UI or SharePoint Designer. There’s nothing to install server-side. You simply need to add references to the core jQuery library and the jQuery Library for SharePoint Web Services and then call the function. See the documentation on the Codeplex site for more details on how to set things up.

SPSetMultiSelectSizes currently takes just one option:

 where multiSelectColumn should indicate the column’s DisplayName on the form.

$().SPServices.SPSetMultiSelectSizes({
    multiSelectColumn: "Web Service Operation"
});

 

I expect I’ll be adding a few more options along the way. A few things that come to mind:

  • Allowing you to specify an absolute width rather than having the function do the calculations for you
  • Specifying whether you want to allow the picker boxes to shrink in size, as they will currently if you have only short values

Conclusion

While this function doesn’t make any calls to the SharePoint Web Services, it seemed like a natural addition to the library. My initial inclination was to automatically call it from $().SPServices.SPCascadeDropdowns, but I’m not sure that makes sense, as it’s pretty simple to call it separately. As always, I’m interested in what you think and what enhancements you’d like to see.

Have a thought or opinion?