4.27.2013

Fixing the Complex Dropdown Bug in SharePoint 2010

The Purpose of this Post
This post will show you how to get rid of pesky Complex Dropdowns from your SP2010 sites once and for all. If you just want the code, skip to the end. If you don't know what a Complex Dropdown is, or why you'd want to get rid of it, read the Quick Recap below.

Quick Recap
When you have a Lookup List that contains 19 or fewer items, SharePoint renders this as a normal dropdown. Simple, clean, elegant.

When your list reaches 20 items or more however, SharePoint has decided to automatically replace this simple, clean, and elegant dropdown with some messy, sloppy, and difficult-to-use Complex Dropdown. This is meant to be an upgrade. The text box, with jQuery behind it, allows users to type the item that they're looking for (which you can actually already do with a dropdown). However, in practice, it is prone to unexpected behavior:

1) You can already click on a dropdown and type, in order to search for an item. The complex dropdown doesn't add any useful functionality.
2) The dropdown doesn't always dismiss right away, forcing you to click more than once. It's slow, and not very helpful.
3) If a developer attempts to add code to pre-select a value, there are unintended side-effects that must be fixed with additional script.
4) In a recent update to SharePoint, a bug was introduced in which all Complex Dropdowns originate on the same location on the page. This means that if you have more than one dropdown on a page, all except one will appear to function incorrectly.

What are the Solutions?
In general, I don't like the complex dropdown for any reason. There are two main ways of solving this problem. This post focuses on the 2nd:
1) If you generate your forms in code using the FormField class, you can set FormField.InDesign = true; before rendering.
2) You can run JS on the page to fix everything. The original solution for this is here: An Egg in the SharePoint World

The Two Minor Shortcomings of the Solution Above
1) It fails on fields that are required to have values. A required Lookup field will automatically pre-select the first value in the dropdown. If the user doesn't change this value, the validator will mistakenly complain that they don't have a value selected. This is wrong for two reasons ... first, a value IS selected, and we probably shouldn't have pre-selected a value in the first place.

I fix this problem by adding an entry of  "Please select one..." at the top of the dropdown.

2) The second issue that this solution doesn't work with cascading dropdowns.

I fix this problem by reworking the code a little bit, and making sure that the values are written back and forth between the hidden complex dropdown, and the new simple dropdown. This is accomplished using the method updateNewField, which is commented below at the bottom of the post.

I wrap all of this into a ScriptLink feature, and simply enable this feature on any site where I don't want to see Complex Lookup Field Dropdowns. Don't know about creating ScriptLink features? More info here: SharePoint 2010's ScriptLink. This is a great way of packaging general-use solutions to problems.




The Code

// Comment
//http://stackoverflow.com/questions/11416099/sharepoint-drop-down-list-doesnt-display-properly-for-more-than-20-items-with-i

DisableComplexDropdowns.FixDropdowns = function () {
    $('.ms-lookuptypeintextbox').each(function () {
        DisableComplexDropdowns.OverrideDropDownList($(this).attr('title'));
    });

}

// Main Function
DisableComplexDropdowns.OverrideDropDownList = function (columnName) {
    // Construct a drop down list object
    var lookupDDL = new DisableComplexDropdowns.DropDownList(columnName);

    // Do this only in complex mode...
    if (lookupDDL.Type == "C") {
        // Hide the text box and drop down arrow
        lookupDDL.Obj.css('display', 'none');
        lookupDDL.Obj.next("img").css('display', 'none');

        // Construct the simple drop down field with change trigger
        var tempDDLName = "tempDDLName_" + columnName;
        if (lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']").length == 0) {
            lookupDDL.Obj.parent().append("");

            var newDDL = lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']");
            $(newDDL).bind("change", function () {
                DisableComplexDropdowns.updateOriginalField(columnName);
            });
        }

        DisableComplexDropdowns.updateNewField(columnName);
    }
};

// method to update the original and hidden field.
DisableComplexDropdowns.updateOriginalField = function (columnName) {
    var lookupDDL = new DisableComplexDropdowns.DropDownList(columnName);
    var newLookupDDL = new DisableComplexDropdowns.DropDownList("tempDDLName_" + columnName);

    // Set the text box
    if (lookupDDL.Obj.val() != newLookupDDL.Obj.find("option:selected").text())
        lookupDDL.Obj.val(newLookupDDL.Obj.find("option:selected").text());

    // Get Hidden ID
    var hiddenId = lookupDDL.Obj.attr("optHid");

    // Update the hidden variable
    if ($('input[name=' + hiddenId + ']').val() != newLookupDDL.Obj.find("option:selected").val())
        $('input[name=' + hiddenId + ']').val(newLookupDDL.Obj.find("option:selected").val());
};

//Call this method from the completefunc in SPServices.SPCascadeDropdowns like so:
//completefunc: function(){
//                if (typeof DisableComplexDropdowns != "undefined" && typeof DisableComplexDropdowns.updateNewField != 'undefined') {
//                    DisableComplexDropdowns.updateNewField(controlTitle);
//                };}
DisableComplexDropdowns.updateNewField = function (columnName) {
    var lookupDDL = new DisableComplexDropdowns.DropDownList(columnName);
    var newLookupDDL = new DisableComplexDropdowns.DropDownList("tempDDLName_" + columnName);

    if (newLookupDDL == null)
        return;

    // Get all the options
    var splittedChoices = lookupDDL.Obj.attr('choices').split("|");

    //If this is a required field, leave a "null" option at the top so that nothing is pre-selected
    if (splittedChoices[1] != "0") {
        splittedChoices.unshift("Select One ...", "0");
    }

    //Determine if the dropdowns are the same. If so, return without changing anything
    var i = 1;
    var different = false;
    newLookupDDL.Obj.children().each(function () {
        if ((i + 1) > splittedChoices.length) //Current dropdown has more items than the old dropdown
            different = true;
        if ($(this).val() != splittedChoices[i])
            different = true;
        i++;
        i++;
    });

    if (!different && (i - 1) == splittedChoices.length)
        return;

    // get selected value
    var hiddenVal = $('input[name=' + lookupDDL.Obj.attr("optHid") + ']').val()
    if (hiddenVal == "0") {
        hiddenVal = lookupDDL.Obj.attr("value")
    }

    newLookupDDL.Obj.children().each(function () {
        $(this).remove();
    });

    // Populate the drop down list
    for (var i = 0; i < splittedChoices.length; i++) {
        var optionVal = splittedChoices[i];
        i++;
        var optionId = splittedChoices[i];

        var selected = (optionId == hiddenVal) ? " selected='selected'" : "";
        newLookupDDL.Obj.append("");
    }
};

// just to construct a drop down box object. Idea token from SPServces
DisableComplexDropdowns.DropDownList = function (colName) {
    // Simple - when they are less than 20 items
    if ((this.Obj = $("select[Title='" + colName + "']")).html() != null) {
        this.Type = "S";
        // Compound - when they are more than 20 items
    } else if ((this.Obj = $("input[Title='" + colName + "']")).html() != null) {
        this.Type = "C";
        // Multi-select: This will find the multi-select column control on 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";
    } else
        this.Type = null;
}; // End of function dropdownCtl


if (typeof $ != 'undefined') {
    $(function () {
        DisableComplexDropdowns.FixDropdowns();
    });
}

5 comments :

  1. Thank you! I was struggling just trying to get a simple dropdown from a lookup column with over 20 items to render in IE. Changing InDesign to true fixed it (for now) but good to know there is another solution.

    ReplyDelete
  2. My opinion differs with you on one very important point:

    To quote you:
    "1) You can already click on a dropdown and type, in order to search for an item. The complex dropdown doesn't add any useful functionality."

    It DOES add helpful functionality. What if you had 500 items in the Parent list? You would see a drop down with 500 items, with most of the list pouring off the screen, inaccessible to mouse clicks. The Complex drop down adds a scroll bar to the list so it could contain an indefinite amount of items. I'm finding this help ful right now.

    ReplyDelete
  3. Which is exactly why it should be a SharePoint Administrator decision or a Developer decision, as an option on the field.

    If you have a static list of 500 items, sure, you might like the complex dropdown. It's a good tool to have. But because SharePoint lists start to break at 5000 items, you don't often use SP to store large amounts of data. And I don't think 20 items is the magic number where people might start wanting to use a complex dropdown.

    I think that if SP were capable of dealing with reasonably large amounts of data, things like complex dropdowns might become more useful, ESPECIALLY if they were changed to be built on top of a normal dropdown, so you could use the same JS to do things like pre-select values and otherwise manipulate the field.

    ReplyDelete
  4. There are quite a few issues with the string concatenations, variable declarations etc. in the script. I've fixed everything and here is the final version -

    http://pastebin.com/jMJ0XzGy

    ReplyDelete
  5. Thanks NLV! Although, you left the "debugger;" statement in at the bottom.

    ReplyDelete