Introduction
Client Side Rendering was introduced in SharePoint 2013 as a main technique for displaying data, replacing XSLT. CSR is now used by default for displaying data from standard lists (exceptions: Survey and Calendar lists) - including list forms, list views and lists in Quick Edit mode, - and search results.
Although CSR is reused for displaying all of those, the process for different data sources differs significantly. I explained CSR basics and specifics of CSR for list views in the previous article: SharePoint 2013 Client Side Rendering: List Views. In this article, I'll focus on list forms: how they work, what are the gotchas, and how to use them. This article contains 4 code examples:
- Primitive example: customizing how field value is displayed
- Primitive example: customizing field control
- Example: dependent fields
- Example: manipulating form layout
CSR for List Forms
List forms in SharePoint 2013 can be rendered using one of 3 modes: Standard, Custom and Server Render:
.
Server Render falls back to XSLT, while Standard and Custom are based on CSR.
Standard Mode
When in Standard mode, ListFormWebPart
renders a template for the form, including the table structure and even field captions. As a placeholder for field values or controls, ListFormWebPart
renders empty span
elements with unique identifiers, like this:
During the page load, a reduced CSR process is launched over and over again for every field in the form, and after each field is rendered, the corresponding span
element will be replaced with the generated by CSR process HTML.
The rendering of the form is handled by method RenderForm
. Reduced CSR process and replacing the span
contents for a given field is performed by method RenderReplace
:
I call CSR in Standard mode reduced, because not all the stages are processed. View, Header, Footer, Body and Group handlers never get executed. Thus it's not possible to change layout of the fields by means of CSR templates. The only way to change the form layout in this mode is to rearrange the already rendered DOM elements e.g. by using jQuery.
Obviously, Standard mode is mostly used to customize rendering of field controls/values.
Primitive Example: Customizing How Field Value is Displayed
The following code customizes, how field value is displayed on a Display form:
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
Fields: {
'Title': {
DisplayForm: function(ctx) {
return '<div class="my-field-title">' + ctx.CurrentItem.Title + '</div>';
}
}
}
}
});
Result:
FormContext
Comparing to list views CSR, list forms CSR process is a bit more complicated, because in addition to displaying, list forms should provide edit capability. It is obvious that in order to perform editing of a value, you have to interact with CSR core, at least providing it with the value that was entered into your custom control.
This is done with the help of ctx.FormContext.updateControlValue(fieldName, value)
function. The ctx.FormContext
object also contains a lot of other useful information for rendering forms and processing their values.
Another useful function of ctx.FormContext
is registerInitCallback(fieldName, callback)
. It's convenient to use it instead of PostRender
, though essentially it does same thing: executes the provided callback right after field is created.
ctx.FormContext
is an instance of the ClientFormContext
class. Below is the full definition of this class:
class ClientFormContext {
fieldValue: any;
fieldSchema: SPClientTemplates.FieldSchema_InForm;
fieldName: string;
controlMode: number;
webAttributes: {
AllowScriptableWebParts: boolean;
CurrentUserId: number;
EffectivePresenceEnabled: boolean;
LCID: string;
PermissionCustomizePages: boolean;
WebUrl: string;
};
itemAttributes: {
ExternalListItem: boolean;
FsObjType: number;
Id: number;
Url: string;
};
listAttributes: {
BaseType: number;
DefaultItemOpen: number;
Direction: string;
EnableVesioning: boolean;
Id: string;
};
registerInitCallback(fieldname: string, callback: () => void ): void;
registerFocusCallback(fieldname: string, callback: () => void ): void;
registerValidationErrorCallback(fieldname: string, callback: (error: any) => void ): void;
registerGetValueCallback(fieldname: string, callback: () => any): void;
updateControlValue(fieldname: string, value: any): void;
registerClientValidator(fieldname: string,
validator: SPClientForms.ClientValidation.ValidatorSet): void;
registerHasValueChangedCallback(fieldname: string, callback: (eventArg?: any) => void );
}
This definition was taken from open source project TypeScript Definitions for SharePoint 2013. Using this project and TypeScript, you can get intellisense and type checking for your CSR code. TypeScript then compiles into JS.
Primitive Example: Customizing a Field Control
This is the code:
function MyFieldControl(ctx) {
var fieldInternalName = ctx.CurrentFieldSchema.Name;
var controlId = fieldInternalName + "_control";
ctx.FormContext.registerInitCallback(fieldInternalName, function () {
$addHandler($get(controlId), "change", function(e) {
ctx.FormContext.updateControlValue(fieldInternalName, $get(controlId).value);
});
});
return String.format('<input type="text"
id="{0}" name="{0}" class="my-input" />', controlId);
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
Fields: {
'Title': {
EditForm: MyFieldControl,
NewForm: MyFieldControl
}
}
}
});
$get
and $addHandler
functions come from ASP.NET Ajax library, as well as String.format
. This library is deployed to every SharePoint page by default. If you want, you can get rid of these functions and replace them with either jQuery or pure JS or whatever else you like.
Now this code will work and it is about as simplified as possible. However, it is recommended to add two more calls into the MyFieldControl template handler: registerFocusCallback
and registerValidationErrorCallback
. This will ensure consistent behavior of your custom field. So here's the final version of the handler:
function MyFieldControl(ctx) {
var fieldInternalName = ctx.CurrentFieldSchema.Name;
var controlId = fieldInternalName + "_control";
ctx.FormContext.registerInitCallback(fieldInternalName, function () {
$addHandler($get(controlId), "change", function(e) {
ctx.FormContext.updateControlValue(fieldInternalName, $get(controlId).value);
});
});
ctx.FormContext.registerFocusCallback(fieldInternalName, function() {
$get(controlId).focus();
});
ctx.FormContext.registerValidationErrorCallback(fieldInternalName, function(errorResult) {
SPFormControl_AppendValidationErrorMessage(controlId, errorResult);
});
return String.format('<input type="text" id="{0}"
name="{0}" class="my-input" />', controlId);
};
So far so good. But how about something more interesting and something more real-world? What if we need interaction between fields, so that when you change one field, another field changes correspondingly?
Example: Dependent Fields
Dependant fields tend to be one of the most wanted features for SharePoint list forms. Now with CSR, we finally got a very good way of implementing them using solely OOTB features.
Consider you have cars, and each car can be of a different color. Every time you select a car, you then should check which colors are currently available for the selected car, and hide colors that aren't relevant.
Something like this:
Probably the simplest way to do it is to use OnPostRender
event. In that case, you don't need to redefine anything - and so you end up with less code; but on the other hand, you have to know how OOTB fields are rendered so that you could manipulate them after render.
So I opened developer tools window in Internet Explorer and quickly found the id of the select control that I can track:
Obviously here field ID
, field type and field name are composed into the control ID
. All this information is available from the context object, so in terms of code it looks like this:
var f = ctx.ListSchema.Field[0];
var fieldControlId = f.Name + "_" + f.Id +
"_$" + f.FieldType + "Field";
Notice I'm using Field[0]
, that's because rendering in Standard mode processes one field at a time (as explained above), and so there is always only one field in ctx.ListSchema
when OnPostRender
event fires.
Once we got the HTML control, we can subscribe to its onchange
event, and every time a change happens, we can then e.g. perform some async event and then based on the received response decide, which colors we should hide.
But how do you hide the radio buttons?
One more time into the developer tools window. Here it is:
So basically, the same structure here. But now the fact that there is only one field in the context plays against me: I don't want to hardcore the field Id, because it is ugly! :) So what can I do?
After some digging, I found that the original ListSchema
is stored in global variable window[ctx.FormUniqueId + "FormCtx"].ListSchema
. Nice, the problem is solved now:
var colorFieldName = "Color";
var colorField = window[ctx.FormUniqueId + "FormCtx"].ListSchema[colorFieldName];
var colorFieldControlId = colorField.Name + "_" +
colorField.Id + "_$" + colorField.FieldType + "Field" + colorId;
Where colorId is 0 = Black, 1 = White, 2 = Green
, etc.
So we have the initial control, subscribed to onchange
event, received some data from external list, got the color field controls, and the only thing left is to disable them and that is easy. So here's the final code!
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
Templates: {
OnPostRender: function(ctx){
var colorField = window[ctx.FormUniqueId + "FormCtx"].ListSchema["Color"];
var colorFieldControlId = colorField.Name + "_" +
colorField.Id + "_$RadioButton" + colorField.FieldType + "Field";
var f = ctx.ListSchema.Field[0];
if (f.Name == "Car")
{
var fieldControl = $get(f.Name + "_" +
f.Id + "_$" + f.FieldType + "Field");
$addHandler(fieldControl, "change", function(e)
{
for (var i = 0;i<5;i++)
$get(colorFieldControlId + i).parentNode.style.display = "none";
var newValue = fieldControl.value;
var newText = fieldControl[fieldControl.selectedIndex].text;
var context = SP.ClientContext.get_current();
context.executeQueryAsync(function()
{
var showColors = [];
if (newText == "Kia Soul") showColors = [0, 2, 3];
if (newText == "Fiat 500L") showColors = [1, 4];
if (newText == "BMW X5") showColors = [0, 1, 2, 3, 4];
for (var i = 0;i<showColors.length;i++)
$get(colorFieldControlId + showColors[i]).parentNode.style.display="";
},
function(sender, args)
{
alert("Error! " + args.get_message());
});
});
} else if (f.Name == "Color") {
for (var i = 0;i<5;i++)
$get(colorFieldControlId + i).parentNode.style.display = "none";
}
}
}
});
And the result:
Example: Manipulating Form Layout
Sometimes, it is necessary to add some additional UI, create sections, or otherwise change the layout of the form. In Standard mode, with the reduced CSR process, layout cannot be simply overridden, as in list views. But it is still possible to do some layout manipulation, and here I demonstrate one method of doing so...
In this example, I will insert an extra row after a certain field in the table.
As I already mentioned, ListFormWebPart
renders special span
elements that later get replaced during the CSR process. These span
elements have simple ids, so we can manipulate them easily. We should do it in OnPreRender
callback, so that the span
is still there and not yet replaced with the field
content.
Once we took hold of a span
element that is related to a certain field, it is easy to e.g. insert something below or above this element. In the following example, I'm inserting an additional row into the form. Obviously, you can put any HTML there, and thus create any kind of extra UI. For example, it can be a map, and by clicking on it user might pre-fill the form values...
So here's the code, it's pretty simple:
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
OnPreRender: function(ctx) {
var fieldName = ctx.ListSchema.Field[0].Name;
if (fieldName == "Title")
{
var span = $get(ctx.FormUniqueId + ctx.FormContext.listAttributes.Id + fieldName);
var tr = document.createElement('tr');
tr.style.backgroundColor = "#ada";
tr.innerHTML="<td colspan='2'> </td>";
var fieldTr = span.parentNode.parentNode;
fieldTr.parentNode.insertBefore(tr, fieldTr.nextSibling);
}
}
});
Result:
Custom Mode
The custom mode should have been the full-scale CSR mode, where both layout, field captions and field controls are generated with CSR.
Unfortunately, based on my investigation, it seems unlikely that Custom mode was really even tested, because the bugs are very obvious and everywhere :(
So here is what happens if you just switch a list form to Custom mode, via Web Part Properties:
Amazing, isn't it? :)
I performed some investigations and after about 3 days of struggle, I ended up with some sort of work around. But I had to write a lot of wrapping code and even after all the efforts, the solution was not perfect and I had to perform additional manipulations via OnPreRender
method in order to fix a minor problem; and also I even had to render the field labels manually...
Overall, I wouldn't recommend switching to Custom mode at all.
Conclusion
CSR for list forms is a great way of solving most common tasks related to list form customizations. It is far from perfect, but it is probably the best way to do it, and also it is the recommended and the supported way. So I would definitely stick to CSR when planning form customizations.
Unfortunately, the mode that is supposed to address layout customizations, doesn't quite work at the time of writing, so the only satisfactory way to do the layout changes now - is to use DOM manipulations, that can be relatively slow in case of big number of fields.
So good luck with your forms!
Update 16.10.2015: After the CustomLayout
mode issue was not fixed even in SP2016 IT Preview, I came up with 50 lines of code that solve the layout problem, enabling fully CSR-compatible custom templates. Have a look:
Also, check out another article I wrote a while ago about using CSR together with KnockoutJs: