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.
Marc,
Love this solution!!!! Great work!
Did anyone get this working in SP 2013? If so, any help would be appreciated! :)
@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.
Does this work in SharePoint 2013?
@Daniel:
The concept, certainly. The exact code, probably not. But it wouldn’t need a lot of adaptation.
M.
Hi Marc:
Almost 9 years on, I had need of this accordion-style script of yours.
Many thanks!!