Using multiple CSR Template Overrides on one Page

SharePoint 2013 ClientSideRendering is a great tool to customize list rendering. But when you put two or more ListViewWebParts on one page and want to use csr, things get complicated. This is because the registered template overrides are executed on all lists, even when you use JS-Link option in WebPart properties.
Microsoft tries to solve this problem by filtering the template overrides by ListTemplateType, BaseViewID and ViewStyle. But when you set up two (or more) custom lists via frontend, the all get ListTemplateType 100. And what happens, when you use two times the same list on one page…?
Now, after more than 2,5 years it was time to solve this problem. (Here is another solution: [1]).

Idea of CSR Framework to support multiple template overrides

My solution is to write one general template override which gets handed over multiple template override objects with filters. By evaluating the filters, the single general template override can decide if the code in an overidden object has to be executed or the default renderer has to be used.

Example: Footer Template Override

I will demonstrate the technique on the footer template. Here is a footer template override:

var leftWebPart_Override = {	
	Templates: {
		Footer: function(ctx) {
			return "Custom WP Footer 1";
		},
	}
};

As you see, this is standard SharePoint notation.
Then I define a CTXFilter:

var leftWebPart_CTXFilters = {		
	ListTitle: "CSR-Test-List"
};

One of my test list has the title ‘CSR-Test-List’.
Now, I register the override using my AtiaNuur.CSR Framework and initialize it:

AtiaNuur.CSR.RegisterTemplateOverrides(rightWebPart_Override, rightWebPart_CTXFilters);
AtiaNuur.CSR.init();

The output is something like this:
AtiaNuur.CSR
You see the customized WebPart on the left and a second uncustomized WebPart on the right. Both are custom lists (with ListTemplateType 100).

AtiaNuur.CSR Framework in detail

So what happens in AtiaNuur.CSR. The framework follows three steps. It doesn’t matter if it’s footer-, header-, body-, OnPreRender, fields- etc. override. Therefore I’ll demonstrate it on the footer template override. You can get the rest out of the code. These steps are:

  1. Filling an array with objects of render-function and filters
  2. Filtering on current context
  3. Calling rendering function

I’ll start in reverse order, as the rendering function is the most interesting and the rest will be easier to understand.

Calling rendering function (templateRenderer)

The CSR Object has two public methods:

  • RegisterTemplateOverrides(templateOverride, ctxFilters): which takes a standard SharePoint template override and CTX Filters.
  • init(): Doesn’t take any arguments. Finally registers the single general template override to the SharePoint SPClientTemplates.TemplateManager.

Let’s have a look at this single general template override. The basic construct is like this (here only for footer template):

var templateOverride = {
	Templates: {
          	Footer: function(ctx) { return templateRenderer(ctx, footers, RenderFooterTemplate); }
     	}
};

You this that it’s always called the templateRenderer function, which takes:

  • ctx: the context
  • footers: an array of objects with footer functions (or a return string) and filters
  • RenderFooterTemplate: SharePoint default rendering function for footer

Now, the templateRenderer iterates over all footers and determines, if the current function in the footer has to be executed or not. This works this way:

function templateRenderer(ctx, templateDefinitions, DefaultTemplate) {
	var returnHTML = null;
		
	templateDefinitions.forEach(function(templateDefinition) {
		if(renderingOnCTX(templateDefinition.CTXFilters, ctx)) {
			if(typeof templateDefinition.Renderer === "string")
				returnHTML = templateDefinition.Renderer;
			else if(typeof templateDefinition.Renderer === "function")
				returnHTML = templateDefinition.Renderer(ctx);
		}
	});
		
	if(returnHTML === null)
		returnHTML = DefaultTemplate(ctx);

	return returnHTML;
}

Filtering on current context (renderingOnCTX)

The renderingOnCTX function determines if the rendering function (templateDefinition.Renderer) has to be executed in current context or not. It simply does this by comparing any parameter in CTXFilters with corresponding parameter in ctx. So you can compare ListTitle, URLs and also WebPart-Number. It is also possible, to execute custom function, by adding them to the CTXFilter object.
Here is the renderingOnCTX function:

function renderingOnCTX(ctxFilters, ctx) {
	var renderingOnCTX = true;
	var ctxFilterNames = Object.getOwnPropertyNames(ctxFilters);
	ctxFilterNames.forEach(function(filterName) {
		if(typeof ctxFilters[filterName] !== "function" && ctx.hasOwnProperty(filterName))
			renderingOnCTX = renderingOnCTX && ctx[filterName] === ctxFilters[filterName] 		
		else if(typeof ctxFilters[filterName] === "function")
		{
			renderingOnCTX = renderingOnCTX && ctxFilters[filterName](ctx);
		}
	});
	return renderingOnCTX;
}

And this is a filter definition with a ctx property comparison and a custom function:

{		
	ListTitle: "Documents",
	FilterFunction: function(ctx) {
		return (ctx.ListTitle.length > 1);
	}
}

You can define as many filter functions as you like. Also the name can be whatever you like.

Filling an array with objects of render-function and filters (RegisterTemplateOverrides)

The last part is now to get create the footers array of objects. This happens in the AtiaNuur.CSR.RegisterTemplateOverrides function (I’m showing just the footer part):

function RegisterTemplateOverrides(curTemplateOverride, ctxFilters) {
	if(curTemplateOverride.Templates !== undefined) {
		if(curTemplateOverride.Templates.Footer !== undefined) {
			footers.push({CTXFilters: ctxFilters, Renderer: curTemplateOverride.Templates.Footer});
		}
	}
}

As you see I create each footers object out of the ctxFilters and the Footer function.

And the other template overrides?

All other template override functions follow the same principle as the footer. The Templates.Fields object has been a little bit trickier, but it also works the same way.

How to use

Add the atianuur.csr.js to your page using masterpage, JS-Link or any other way you like. I used the masterpage, and also added a csr.js file which defines the concrete template override:

<SharePoint:ScriptLink language="javascript" name="~sitecollection/Style Library/atianuur.csr.js" OnDemand="false" runat="server" Localizable="false" LoadAfterUI="false" />
<SharePoint:ScriptLink language="javascript" name="~sitecollection/Style Library/csr.js" OnDemand="false" runat="server" Localizable="false" LoadAfterUI="true" />

I uploaded both files to the style library (any other folder works as well). Then I load the AtiaNuur.CSR framework before and the csr.js file after the UI.

And here is some code out of the csr.js file, which hands over a standard template override and a CTXFilter object:

var leftWebPart_Override = {	
	Templates: {
		Header: "",
		Footer: function(ctx) {
			return "Custom WP Footer 1";
		},
		Fields: {
			"LinkTitle" : {
				View: "This is not the original link text"
			}
		}
	},
	OnPreRender: function(ctx){ alert("I'm prerendering"); }
};
var leftWebPart_CTXFilters = {		
	ListTitle: "CSR-Test-List"
};
AtiaNuur.CSR.RegisterTemplateOverrides(leftWebPart_Override, leftWebPart_CTXFilters);

In this example, I also overwrite the LinkTitle-Field and the OnPreRender function.

Disclaimer

The code to get the default renderer comes from Jim Brown [2].

Sources

And here are the complete sources: atianuur.csr.

References

[1]: http://www.myfatblog.co.uk/index.php/2013/09/listview-web-part-issues-with-jslink-and-display-templates-a-solution/
[2]: http://sharepoint.stackexchange.com/questions/112506/sharepoint-2013-js-link-return-default-field-rendering

Leave a Reply

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