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:for-each>
   </table>
</xsl:template>

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

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);
    else
        Branches.choices = "(None)|0";        
    Branches.value = "";
}

47 Comments

  1. Thanks for the post !! I wanted a dynamic cascading dropdownlist.

    I am facing a javascript error in “var savedSectionUnitChoices = Branches.choices + “|”;” where Branches.choices is giving me an “undefined”.
    The lookup column is not refered as “select” but it’s “input”. Is the error because of that.
    Please help me here !! and if possible could you post the entire javascript in one shot.

    Cheers
    Akshay

    Reply
    • Akshay:

      Glad you find it helpful. You will need to have the getTagFromIdentifierAndTitle function which you can find in other posts. You’ll also need to adapt this to your specific columns, etc., of course.

      I also noticed that I had a mistake in my code: savedSectionUnitChoices should be savedBranchesChoices. (I had converted from the client’s terms to more generic ones for this example and missed that one spot.)

      M.

      Reply
  2. Thanks for the article! – couple of issues..if the lookup list contians less than 20 values i have to use “Select” else “input”. I used 2 site columns with less than 20 values…so i have changed the code to “select” for Branch column. If both are select it’s showing an undefined error…it’s working only if i change one to “input’ and other “select”….

    How to carry the savedBrahcesChoices to DivisionChanged Event?

    Thanks,
    Andy

    Reply
    • Sure, Andy. You’ll have to make some changes based on what your dropdowns actually contain. You’ll also find that the code will need further adjustment if there are fewer than 20 “Branch” choices. What I’m showing is the more complex code required if there are more than 20 because of the difference in how the dropdown is rendered.

      M.

      Reply
  3. Thanks for the reply Marc, but for lookup list having lesser than 20 items, how to get the list of choices to the DivisionChanged function.
    wil “Branches.options” work as below ,

    var Branches = getTagFromIdentifierAndTitle(“select”,”LookUp”,”Branch”);
    Branches.options….

    Thanks,
    Akshay

    Reply
    • Akshay:

      I’m getting quite a bit of interest in the post, so I think I’ll work up a more generically useful post over the weekend. In the meantime, you’re on the right track. Use the Internet Explorer Developer Toolbar (IE7 or below) or Developer Tools (hit F12 in IE8) to look into the DOM to understand what your code should look for in the page.

      M.

      Reply
  4. Is there a working example that we can see?

    The pieces of javascript, is that all that’s need to make it work?
    I don’t understand where you put each piece of js in the page.

    also want to make sure I have these right.

    “Division” this is the name of the main dropdown? rename to what our dropdowns are named
    “Branch” this is name of the sub dropdown? rename to what our dropdown are named

    “DivisionTable” This is the name of the DVWP that is inserted.

    those should be the only things we need to change right to make it work right?

    Is there anything else that’s needed to be done?

    Reply
    • I always prefer to show people where the fishing rods are and explain how they work a little, but as I mentioned to Akshay above, I’ll try to post something a bit more generic this weekend.

      But basically, Chris, you’re also on the right track.

      M.

      Reply
      • If this is a repost, I’m sorry I don’t think my first reply got posted.

        I’m looking forward to seeing the more generic version, my main problem is where to put the javascript.

        I’m trying to insert it inline now, just to see if I can get the page working and so far haven’t had much luck.

        Also, I think this is a sharepoint thing, but I’m not really sure which name to use in the script.
        I’m looking in the code and see
        <!– FieldName="Sub_Category"
        FieldInternalName="Sub_Category" <

        but one line down I see something like this.
        <select name="ctl00$m$g_28f4fcc6_2a99_414f_90c9_db20930d4d04$ctl00$ctl02$ctl00$ctl01$ctl0.. and it goes on for another 100 or so characters!!

        I've been trying to use the FieldInternalName, but maybe I need to use that 200 character name that SP gives it.. ugh.

        anyways
        Thank you again for writing this, and looking forward to seeing the next version and hopefully I can get it working.

        Reply
  5. hmm, I should have posted here for problems instead of the other tutorial.

    I got it to work with < 20 items in my second list.
    But now I have exactly 20 (figures) items in my second dropdown (Sub_Categories)

    So now I'm trying to make the above code work on my site.

    Am I on the right track?
    My first DropDown is named 'Category' and has about 4 items.
    My second Dropdown is named 'Sub_Category' and currently has 20 items.
    My DVWP is still named Column1Table.

    My js currently has this
    var Branches = getTagFromIdentifierAndTitle("input","","Category");
    var savedBranchesChoices = Branches.choices + "|";
    Division = getTagFromIdentifierAndTitle("select","","Sub_Category");

    I changed the reference to the table in the second part of the js
    var DivisionTable = document.getElementById("Column1Table");

    So right now, I'm getting a js error
    saveBranchChoices is undefinded

    but doesn't this line define it?
    var savedBranchesChoices = Branches.choices + "|";

    Reply

Have a thought or opinion?