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. @Paudy:

      Pages in 2013 are structured somewhat differently. You should look at this post – along with any others – as an example of how you could do something. Your needs will probably vary, anyway, so you’ll need to customize the code.

      M.

  1. Hi Marc:
    Almost 9 years on, I had need of this accordion-style script of yours.
    Many thanks!!

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.