Click here to Skip to main content
Click here to Skip to main content

SharePoint 2013 Client Side Rendering: List Forms

, 28 Aug 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Client Side Rendering specifics related to list forms.

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, but 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. 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. 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) {

    // Instead of hardcoding the field value, we can fetch it from the context
    var fieldInternalName = ctx.CurrentFieldSchema.Name;
    var controlId = fieldInternalName + "_control";

    // Initialization of the field: here I'm attaching onchange event to my control,
    // so that whenever text is changed, FormContext.updateControlValue is executed.
    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 IE 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 it's 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! Smile | :) 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)
          {
              // first, let's hide all the colors - while the information is loading
              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();
              // here add logic for fetching information from an external list
              // based on newText and newValue
              context.executeQueryAsync(function()
              {
                  // fill this array according to the results of the async request
                  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];

                  // now, display the relevant ones
                  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") {
          // initialization: hiding all the choices. first user must select a car
          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 overriden, 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'>&nbsp;</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? Smile | :)

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

In 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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Andrei Markeev

Finland Finland
SharePoint MVP, one of the top experts at SharePoint StackExchange, blogger, author, speaker, opensource contributor.
Follow on   Twitter   Google+

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141220.1 | Last Updated 29 Aug 2014
Article Copyright 2014 by Andrei Markeev
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid