Taming Long SharePoint List Forms Using jQuery to Break Them into Sections

Do you have any lists in SharePoint where the forms seem to go on and on, marching down the page, seemingly ad infinitum? I’ve seen lists which have over a hundred, even over 300 columns. This is a horrible thing to do to your users for so many reasons, but one of the primary reasons is that the list forms are simply unmanageable and in many cases unfathomable.

If you do have that many columns in a list and they really do need to be there, consider building custom “sub-forms” which take a more wizard-like approach. What I mean by this is that you might have a relatively short initial, customized version of the NewForm which collects all of the required information. When your user saves that form, you can use SPRedirectWithID from SPServices to go to the next form in the sequence, and so on. You could, of course, develop totally custom forms, whether by using traditional .NET development or by using jQuery, and probably jQueryUI plus other plugins, and the SPServices and the Web Services to write the data into your list(s).

But what if all of that seems overly complex or beyond your skills? There’s a quick thing you can do to tame those ridiculously long forms a tiny bit. Your users still won’t love you, but they may not hate you as much.

The first thing I needed to do for this post was to create a new list with lots of columns. I didn’t have any of these horrid complicated lists lying around in my test environment.) I could have done this any number of ways, including manually, but I decided to write some jQuery with SPServices to do it.

<script type="text/javascript" language="javascript" src="jQuery Libraries/jquery-1.6.2.min.js"></script><script type="text/javascript" language="javascript" src="jQuery Libraries/jquery.SPServices-0.6.2.min.js"></script>
<script type="text/javascript" language="javascript">
  $(document).ready(function() {

    // Delete the old list (if present)
    $().SPServices({
      operation: "DeleteList",
      listName: "Complicated List",
      completefunc: function (xData, Status) {
        alert(xData.responseText);
      }
    });

    // Create the new list
    $().SPServices({
      operation: "AddList",
      listName: "Complicated List",
      description: "List with lots of columns",
      templateID: "100",  // Custom list
      completefunc: function (xData, Status) {
        alert(xData.responseText);
      }
    });

    var newFields = "<Fields>";

    for(i=1; i <= 100; i++) {
      newFields += "<Method ID='" + i + "'>" +
        "<Field Type='Text' DisplayName='Column_" + i + "' FromBaseType='TRUE' MaxLength='255' Description='Description of Column_" + i + "' />" +
        "</Method>";
    }
    newFields += "</Fields>";

    // Add a lot of columns to the list
    $().SPServices({
      operation: "UpdateList",
      listName: "Complicated List",
      newFields: newFields,
      completefunc: function (xData, Status) {
        alert(xData.responseText);
      }
    });

  });
</script>

This script simply creates a new list for me called Complicated List and adds 100 columns to it named Column_n. It was down and dirty, but you might see a more complex and useful version of this this as a poor man’s templating tool for building lists in your own environment. And yes, I’m doing this in good old WSS 3.0. It’s not such a bad place to be, and the same approach and code will work in SharePoint 2010. Isn’t SPServices grand?

Once I had my list to play with, I copied NewForm.aspx to NewFormCustom.aspx  – never, ever edit the default list forms – and set it as the form to use when creating a new item for the list. Then I added my CSS and script to NewFormCustom.aspx.

<script type="text/javascript" language="javascript" src="../../jQuery Libraries/jquery-1.6.2.min.js"></script><script type="text/javascript" language="javascript" src="../../jQuery Libraries/jquery.SPServices-0.6.2.min.js"></script>
<script type="text/javascript" language="javascript">
  // This function from SPServices gets all of the Query String parameters
  var queryStringVals = $().SPServices.SPGetQueryString();
  // We can pass in a section name on the Query String to expand by default
  var expandSection = queryStringVals.section;

  $(document).ready(function() {

    // Set up each of the sections -
    // see the setupSection function below for an explanation of the parameters
    setupSection("Section1", "This is Section One", "Title", "Column_5");
    setupSection("Section2", "This is Section Two", "Column_6", "Column_15");
    setupSection("Section3", "This is Section Three", "Column_16", "Column_25");
    setupSection("Section4", "This is Section Four", "Column_26", "Column_40");
    setupSection("Section5", "This is Section Five", "Column_41", "Column_80");
    setupSection("Section6", "This is Section Six", "Column_81", "Column_100");

    // If no section on the Query String, expand the first section
    if(expandSection === undefined) {
      $("tr[section-name='Section1']").css("display", "block");
      $("tr#Section1").addClass("demo-collapse");
    }

  });

  function setupSection(sectionShortName, sectionLongName, startColumn, endColumn) {
    /* Set up a form section
      Parameters:
        sectionShortName: This short name is used for element IDs and such. It cannot contain spaces or special characters.
        sectionLongName: This name is what is shown in the section header and can contain any text.
        startColumn: The first column in the section.
        endColumn: The last column in the section.
      The two column names should be the Display Name, e.g., "Product Name", not the InternalName, e.g., "Product_x0020_Name"
    */

    // Find the first and last row in the section and add the section header
    var sectionStartRow = findFormField(startColumn).closest("tr");
    var sectionEndRow = findFormField(endColumn).closest("tr");
    $(sectionStartRow)
      .before("<tr class='demo-section-header' id='" + sectionShortName + "'>" +
        "<td class='demo-section-header-cell' colspan=2>" + sectionLongName + "</td>" +
        "</tr>");
    var thisSection = $(sectionStartRow).nextUntil(sectionEndRow);

    // Give all of the rows in the section an attribute for the section name for easier manipulation later
    $(sectionStartRow).attr("section-name", sectionShortName);
    $(sectionStartRow).nextUntil(sectionEndRow).attr("section-name", sectionShortName);
    $(sectionEndRow).attr("section-name", sectionShortName);
    $(sectionEndRow).find("td").addClass("demo-section-last-row-cell");

    // Hide all of the rows in the section
    $("tr[section-name='" + sectionShortName + "']").css("display", "none");

    // Add the click behavior for the section header
    $("tr#" + sectionShortName).click(function() {
      var thisSectionRows = $("tr[section-name='" + sectionShortName + "']");
      if($(this).next("tr[section-name='" + sectionShortName + "']").css("display") == "block") {
        $(thisSectionRows).css("display", "none");
        $(this).removeClass("demo-collapse");
      } else {
        $(thisSectionRows).css("display", "block");
        $(this).addClass("demo-collapse");
      }
    });

    // Expand the section if there are any validation errors
    $("tr[section-name='" + sectionShortName + "'] .ms-formbody .ms-formvalidation").each(function() {
      if($(this).html().length > 0) {
        $("tr[section-name='" + sectionShortName + "']").css("display", "block");
        $("tr#" + sectionShortName).addClass("demo-collapse");
        // No need to look at any more rows
        return false;
      }
    });

    // Expand the section if it's been passed on the Query String
    if(sectionShortName == expandSection) {
      $("tr[section-name='" + sectionShortName + "']").css("display", "block");
      $("tr#" + sectionShortName).addClass("demo-collapse");
    }

  }

  // This function (borrowed from SPServices) finds a column's formBody in the page
  function findFormField(columnName) {
    var thisFormBody;
    // There's no easy way to find one of these columns; we'll look for the comment with the columnName
    var searchText = RegExp("FieldName=\"" + columnName.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "\"", "gi");
    // Loop through all of the ms-formbody table cells
    $("td.ms-formbody").each(function() {
      // Check for the right comment
      if(searchText.test($(this).html())) {
        thisFormBody = $(this);
        // Found it, so we're done
        return false;
      }
    });
    return thisFormBody;
  } // End of function findFormField
</script>

And the CSS:

.demo-hidden {
	display:none;
}
.demo-main {
	height:250px;
	overflow:scroll;
}
.demo-no-item-selected {
	font-size:12px;
}
.demo-section-link {
	padding-left:15px;
	font-size:10px;
}
.demo-section-header {
	background-image:url('/_layouts/images/plus.gif');
	background-repeat:no-repeat;
	background-position:5px center;
	padding:3px 3px 3px 22px;
	background-color:#6699cc;
	font-weight:bold;
	color:#ffffff;
}
.demo-section-header-cell {
	border-top:1px #c2c2c2 solid;
}
.demo-section-last-row-cell {
	border-bottom:2px black solid;
}
.demo-collapse {
	background-image:url('/_layouts/images/minus.gif');
}

Et voila, expando-collapso.

If you’d like to see this working for real, you can take a look at this page on my demo site.

Don’t you hate it when forms “hide” the field that you need to fix? It happens way to often on forms out the on the world wild Web. So note that if there’s a validation error on the form that the script pops open that section to bring it to your attention. Column_13 is required on the demo site. Try saving the form without filling it in.

And finally, if you choose to pass in a section’s short name on the Query String, that section will be expanded instead of the first. Try ?section=SectionN, where N is [1-6].

Note: The CSS I’m using here in my little demo is intentionally very simple. When I built this for real, we had some nice little icons and backgrounds that made it feel even more helpful.

Similar Posts

45 Comments

  1. Also using 2010 – once I applied @Ankur’s addition, I also had to delete the Expand feature before it all worked for me (so my implementation doesn’t support passing a section in the querystring).

  2. Hi Marc,
    I am trying to implement this code and it is not working. I am adding it through CEWP.I have a question if the field names have space or symbols will that stop the code from working.
    Please if you can explain how should I go about troubleshooting the issue that would be very helpful and appreciated.

  3. I implemented something like this and added session cookies to note which sections were expanded. So when users returned to the form, it was set the same way that they had left it. In a not very elegant way:

       var g2 = GetCookie("div#WebPartWPQ2");
      if (g2 &amp;&amp; g2 == "open") {  
      	  $('div#WebPartWPQ2 &gt; div.tableTitle').children('span').children('img').toggleClass('isoff');
    	  }
    	  else
    	  {
         $('div#WebPartWPQ2').children('table').hide();
          g2="close"
          SetCookie("div#WebPartWPQ2", g2, "/");
    	  }
     $('div#WebPartWPQ2 &gt; div.tableTitle').click(function(){
                       $(this).children('span').children('img').toggleClass('isoff');
                       $('div#WebPartWPQ2').children('table').slideToggle('fast','swing');
    				   if (g2=="close") {
    				     g2="open";
    					 SetCookie("div#WebPartWPQ2", g2, "/");  
    					 }
    					 else
    					 {
    					 g2="close";
    					 SetCookie("div#WebPartWPQ2", g2, "/");
    					 }
                       return false;
                       });
    
  4. I believe that I used the SharePoint cookie functions at the time, as there is no reference to an external library like jQuery.cookie.js in the form that I modified.

    Function SharePoint JavaScript Library
    GetCookie(sName) INIT.JS, BFORM.JS
    SetCookie(name, value, path) CORE.JS,OWS.JS

    I don’t think that I would necessarily recommend this approach today, but it was simple and it worked. And, in the end, the users really hated the long list and opted for something much simpler, and so the code was not used beyond the demonstration session.

  5. Hi,
    I’m not very proficient in coding. Should this work in SharePoint Foundation 2010 or should there be changes to the script code/functions? Attempting to use in customized forms, but getting ‘undefined’ is null or not an object error at the var sectionStartRow = findFormField(startColumn).closest(“tr”); line.
    Any help would be much appreciated. Thank you!

    1. Lost One:

      The forms haven’t changed much from 2007 -> 2010 -> 2013 even. That said, you should consider any code you find on my blog (or elsewhere) an example. You may need to do some debugging, adaptation, etc. to meet your own needs.

      M.

    2. I realize this is an old post but trying to implement this and having the same issue. Did anyone ever find a resolution to this?

  6. Just a quick tip for anyone that’s applying this within a branded deployment which may use JQuery. I had to replace the $ shortcut with the JQuery keyword using a mass find/replace.

    Doing so however, broke the regular expression in the findFormField function

  7. Good morning Marc, i was wondering if you could point me in the right direction, a couple of months ago i saw a thread that had some code to be able to make repeating fields in sharepoint, unfortunately i was not able to find the same thread again and i was hopping you’d give me the link or let me know if this achievable or not sharepoint 2010, what im looking for is something like the Kwizcom forms third party solution.
    thanks in advance and have a blessed day.

    1. Cesar:

      It’s possible to create a master/detail relationship between items in two lists. I think Mark Rackley has a good post on that on his blog.

      Otherwise, many people turn to InfoPath for this.

      M.

      1. I apologize for bothering Marc but would you happen to have the link to the post you have mentioned.

  8. Hi Marc,

    Thanks for sharing the code, but I have added buttons in between the columns, and the code is not working.
    Can you please help me in this so that i can have expand and collapse functionality again.

    1. @Jodi:

      Yes, you can do this sort of thing in any version of SharePoint. Since I wrote this post back in the SharePoint 2007 days, you’ll undoubtedly need to make adjustments to the script, but the idea will work. The easiest way to add the code is to add a Content Editor Web Part into the form page and reference a file in the Content Link.

      M.

  9. Hi Marc .. instead start column and end column can we user some specific columns in Tabs..bcz the example is not real scenario.. i.e setupSection(“Section1”, “This is Section One”, {“Title”, “Column_3”});
    setupSection(“Section2”, “This is Section Two”, {“Column_1”, “Column_2”, “Column_”4 });

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.