Tabbed Forms with Client-side Rendering (CSR)

In this post, we’re going to look at how to implement tabbed forms using CSR. Unlike our previous examples of field rendering, which generally depend on only one field, tabbed forms are going to depend on all fields in the form. For this reason, it makes sense that we’re going to inject our JavaScript by setting the JSLink property of a content type. Content types are, after all, basically just a collection of fields. There are some gotchas’, or at least things you should be aware of when setting the JSLink on a content type, which I’ll cover as I get to them.

Giving credit where it’s due, the code for this rendering template is an adaption of a tabbed forms rendering template that is available in the Office Developer Center samples for Client-side Rendering. I’ve included a link to that article in the references below. Like most tutorials, deployment is left up to you in that article, and it’s assumed you’ll just add content editors to your forms to get the script loaded. I’ll include a utility for setting the JSLink property on a content type, which is a much better solution with some caveats.

I’m building on the same list I’ve been using in my previous CSR posts, with the same 7 fields already configured with the templates I showed in the previous posts. Below is the skeleton for my CSR template. There’s nothing really new here, I’m overriding OnPreRender and OnPostRender. Remember that both of these methods are called back for each field in the form. I’ll implement the preRender and postRender methods later on in the post. The configuration is an object. Each tab is a property off of that object and the property name is displayed on the tab. The property value is an array of internal names for fields that are displayed when the tab is active. Note that any field that is not on any tab is always hidden in the tabbed forms, in this particular implementation.

Now the preRender method does most of the work. It inserts the HTML DOM that will form the structure of the tabbed forms. It also inserts the CSS to make this structure look like tabbed forms in the browser. Here is the code:

This code performs the following operations:

  1. It creates an unordered list with a list item for each tab, and inserts the tab text (i.e. the property name) into the item as an anchor tag.
  2. It then inserts the CSS into the DOM to make the unordered list look like the tabs in tabbed forms, by calling the method getCss(). This method uses a slightly modified mulitline trick, like the one I used in the last post to inject CSS, so there’s nothing particularly new or interesting here. As such, I’m not going to clutter up this post by showing it here. It’s available in the download.
  3. Next, it attaches a click event handler to each tab anchor to select the current active tab. I’ll show the selectTab method below.
  4. And finally, it selects the first tab (index 0) on document ready, using the same selectTab method.

And here is the selectTab code:

selectTab is passed a zero-based index and does the following:

  1. Finds the anchor tag for the index, and adds the active class to it’s parent list item. This changes the background color of the active tab.
  2. It also removes the active class from any siblings of the active list item, which reverts the background color of the previously selected tab.
  3. It then hides any elements on the form web part with an id that starts with ‘tr_’.
  4. And finally, it shows every element on the form web part with an id equal to ‘tr_[internalName]’ where internal name is one of the names in the array of field names for the current active tab.

Now if you have any experience modifying SharePoint forms, you’re probably asking ‘what is this id starts with ‘tr_’ nonsense? The table rows for SharePoint form fields don’t usually have an id at all. That’s what the postRender method (shown below) is for. It adds an id attribute with a value of ‘tr_[internalName]’ to each table row just after the row is rendered, making it easier to find them later on when I need to show and hide them.

That’s it for the code. Now we need to get it deployed. If you haven’t already done so, you should create a site content type, with the fields you want to display on your tabs. Then enable allow management of content types in your list settings, and add your new site content type to the list (and probably remove any other content types, although if you already have items in your list, you’ll need to either delete them or change their content types before you can delete that content type). Here is the content type I’ve created:

CSRDemos Content Type

Now that we have our content type, we need to set the JSLink property of our content type to load our tabbed forms rendering template. I’ve included a small utility page in the source called SetJSLinkOnContentType.aspx that will enable us to accomplish this task. Just drop the file in your style library and click on it. Fill it out as shown below, and click the “Set JSLink” button:

Set JSLink on Content Type Form

Now if you open up your new or edit form, you should prepare to be disappointed. Nothing changed, as in, no tabs appear on the form and all fields are displayed as normal. So what gives? Ok, that was a bit of a setup, but it demonstrates the first quirk you need to be aware of when setting the JSLink property of a content type.

What happens when you add a site content type to a SharePoint list? SharePoint creates a new list content type that inherits from your site content type. This is what allows you to make changes in the list without affecting the site content type and all other consumers of the content type.

This is also why, when you modify the site content type, there is a radio button that says ‘Update all content types inheriting from this type?’ But this actually appears when you try to add, edit, or remove a field from the content type. The reason is that these are the only changes that can be pushed down to child content types.

So when I update the JSLink of the site content type, I call contentType.update(true), and that boolean argument is telling the object model that I want to push my changes down to child content types. But the JSLink property does not get pushed down no matter what I do.

There are basically two methods to overcome this. I can remove my content type from the list and then re-add it. This will bring in the updated JSLink. But this is an obvious PITA, because I can’t remove the content type if I have any items that are using it, so what now? Am I supposed to delete all of my data? The alternative is that I can set the JSLink property programmatically on the list content type just like I can on the site content type.

In the form above, notice that I have a third input for List that I left empty. If I choose my list and hit Set JSLink again, it will now set the JSLink on both the site and the list content type. Now if you’re using the content type in a bunch of lists, this is still a PITA because you need to set the each list content type, one at a time (at least given the functionality in my utility page).

The code could be modified to allow you to set multiple lists to update, or even walk the site collection and update each list content type that is based on the site content type. At the moment, at least until I need it, this is left as an exercise for the reader. If you have a large site collection, this could take a long time or even timeout. If you need this functionality under those circumstances, it might be better to write a PowerShell script, assuming you have sufficient access to run it and/or to meet it’s dependencies (i.e. install the client SDK on your desktop if you’re not a farm admin).

Anyway, now that I’ve set the JSLink on the list content type, if I go to the new or edit forms I get a tabbed form as expected:

Example of Tabbed Forms

The second quirk of JSLink attached to content types does not really affect us for the purpose of this display template, but you need to be aware of it because it does limit the usefulness of setting JSLink on content types in order to load rendering templates. The JSLink property of a content type only gets loaded on forms. It never gets loaded on views, regardless of whether some, most, or all of the items in the view are using that content type. That doesn’t affect us here because we wanted tabbed forms, not tabbed views. Still, it’s kind of a bummer. There are other pretty nifty ways to take complete control of a view using CSR display templates, and I’ll be looking at that in my next post.


Leave a Comment