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.

27 Comments

  1. You mentioned you added the CSS and script to NewFormCustom.aspx.
    In what part of NewFormCustom.aspx should the script go? And in what part of NewFormCustom.aspx should/could the CSS be placed?

    Reply
  2. I am very new to this and I apologize if I sound stupid: I tried using the CEWP webpart but I wasn’t sure which one to link since there are two files (one js and the other css) as it could only take one link. My other option was to go with your first suggestion, which is to “add the script and CSS right below the PlaceHolderMain”. I still am not sure where. I am using SharePoint Designer and on this list, I found this:

    Is it supposed to be right after this line or between PlaceHolderMain and runat that I place both the CSS and the js script?

    Reply
  3. Hi Marc,

    To my previous query regarding SharePoint 2010 issue, i have found a fix. and the fix is that i have added this in your code:

    $(document).ready(function() {
    $(“.ms-formtable”).find(“tr”).css(“display”, “none”);

    //rest of the code…

    });

    Then it worked for me.
    Hope you will give a better solution for 2010…

    Reply
  4. Hi Marc,
    How do you do the following described above? I do not have Share Point Designer, we are forced to work with it stictly OOB..

    “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.”

    Reply
  5. Marc,
    Have you attempted to add this expand/collapse capability using the InfoPath form editor? I’ve been able to get it to work using a Checkbox control to control the expanding/collapsing of a section, but I would prefer to get it to work with the +/- images instead.

    Reply
  6. Hi Marc,

    I am trying to filter the drop down values of a list Newform on basis of some condition. This i have done using jquery by adding a CEWP in the form. The code works fine. But when i click on apply button of the CEWP it redirects to an error page.
    Can you pls guide me what might be wrong.

    Thanks,
    Binita

    Reply
  7. Binita:

    Without knowing what the error is, it’s hard to say. My guess is that you have an error in your script.

    It’s best to use the Content Link to reference script stored in a separate file.

    M.

    Reply
  8. Hi Marc,

    thanks for your solution described above. Maybe you could add the header and footer for the CSS section, I was scratching my head over this a while but in the end it worked like that:

    ….

    Anyway … unfortunately I am not able to use the “full-functionality” of rich-text-columns, while I include them into the sections. As long as the richt-text-columns are outside of a section, you can for example use text-marker and other fency color-stuff. This is not possible any longer, when included in a section. this functions can be selected in the “format-ribbon”, but simple do not work any longer. Do you have any idea?

    Thanks in advance.

    Reply
    • Marcel:

      I just added a Rich Text column to my demo page and it seems to work just fine. Can you explain more about the issues youre seeing?

      Also, I’m not sure what you mean by “the header and footer for the CSS section”.

      M.

      Reply
  9. Hi Marc,

    Great work as always. Quick question for you though.

    What determines the spacing between the plus/minus gif images? They’re overlapping with the section headers I’ve inserted and editing the demo-section-headers / demo-collapse CSS classes (the padding rule) hasn’t yielded any results.

    I’ve applied this inline too, so I can’t think of it being overidden anywhere.
    The corporate environment uses IE8.

    Any ideas appreciated.

    Many thanks,
    Steve

    Reply

Leave a Reply