Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / SharePoint / SharePoint2013

SharePoint 2013 Client Side Rendering: List Forms + Layout

5.00/5 (4 votes)
15 Oct 2015CPOL4 min read 57.3K   440  
Tiny micro-templating engine fully compatible with SharePoint CSR

Introduction

SharePoint CSR for List Forms has two modes: Standard and CustomLayout. Unfortunately, the latter which is intended for forms with custom layouts, doesn't work (and btw, this is still not fixed in SP2016).

I came up with a small JS code snippet that solves this problem and enables creating completely custom form layouts (this works in Standard mode, no need to switch into CustomLayout).

Usage Example

This is the form that will be customized:

Image 1

And this is the template (it is added to the page via Script Editor):

HTML
<div style="display:none" data-role="form">
  <h3>
    <span data-field="Title">City of <strong>{Value}</strong></span>
    (<span data-field="Country">{Value.split(';#')[1]}</span>)
  </h3>
  <hr>
  <div data-field="*" class="my-formfield">
      <label>{Title} <span style="{ Required ? '' : 'display: none' }">*</span></label>
      <span data-role="field-control" />
  </div>
</div>

Then, also on this page is included the 50-lines js file that you'll find below.

And here's the result:

Image 2

There's also some minimal additional CSS that I added to the page along with the template itself:

HTML
<style>
.my-formfield {
   padding-bottom: 4px;
}
.my-formfield > label {
    font-size: 14px;
    width: 115px;
    display: inline-block;
    vertical-align: top;
}
.my-formfield > span {
    display: inline-block;
    vertical-align: top;
}
</style>

Show Me the Code!

Ok, and now, here's the code that processes this template and does the customization:

JavaScript
SP.SOD.executeFunc("clienttemplates.js", "SPClientTemplates", function() {

  function init() {
    var templates = {};
    
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides({

      OnPreRender: function(ctx) {
        if (!ctx.FormContext)
          return;
        var listId = ctx.FormContext.listAttributes.Id;
        var templ = templates[listId] || document.querySelector
        ("[data-list-id='" + listId + "']") || 
        document.querySelector("[data-role='form']");
        if (!templ)
          return;
        if (!templates[listId])
        {
          var table = document.querySelector("#WebPart"+ 
          ctx.FormUniqueId + " .ms-formtable");
          table.style.display = 'none';
          table.parentNode.insertBefore(templ, table);
          templ.style.display = '';
          templates[listId] = templ;
        }
        var field = ctx.ListSchema.Field[0];
        var el = document.querySelector('tr > td > span#'+
        ctx.FormUniqueId + listId + field.Name);
        var target = templ.querySelector("[data-field~='" + 
        field.Name + "']") || templ.querySelector("[data-field='\*']");
        if (target && el && field.Name != "Attachments")
        {
          if (target.attributes['data-field'].value=='*' || 
          target.attributes['data-field'].value.indexOf(' ') != -1)
          {
            target.style.display = 'none';
            var cloned=target.cloneNode(true);
            cloned.setAttribute("data-field", field.Name);
            cloned.style.display = '';
            target.parentNode.insertBefore(cloned, target);
            target = cloned;
          }
          var html = target.innerHTML;
          field.Value = Encoding.HtmlEncode(ctx.ListData.Items[0][field.Name]);
          html = html.replace(/{[^}]+}/g, function(m) { with (field) return eval(m); });
          target.innerHTML=html;
          var control = target.querySelector("[data-role='field-control']");
          control && control.parentNode.replaceChild(el, control);
        }
      }
      
    });
  }

  RegisterModuleInit(SPClientTemplates.Utility.ReplaceUrlTokens
  ("~siteCollection/Style Library/customlayout.js"), init);
  init();

});

The code is pretty straightforward:

  1. Find the template
  2. Bring template next to original form
  3. Hide original form
  4. Show template
  5. Replace stuff in template
  6. Done

Please feel free to change the code to adapt it for your own needs! :)

"Installation"

Copy the code and include it to the page using any method you like. It is also safe to include it in the site scope (e.g. via masterpage or custom action) and use it along with any other CSR code.

If your site has MDS (Minimal Download Strategy) enabled, don't forget you cannot include JavaScript inline, as this breaks MDS. So create a separate js file, put code there, upload the file e.g. to Style Library, and include it to the page e.g. using JSLink of the ListFormWebPart.

Template Documentation

Actually, you can integrate CSR with existing templating engine, but my idea was to create a snippet that doesn't have dependencies and which I could quickly copy-paste into the page and start working. Below is the documentation for that micro-engine.

So element with attribute data-role="form" will be the template. Please also add style="display: none" for better user experience, so that initially the template is hidden.

If you have several forms on one page, add data-list-id attribute and put id of the list there:

HTML
<div style="display:none" data-role="form" 
data-list-id="YOUR-LIST-GUID-HERE">
  <!-- ... -->
</div>

You can insert your HTML template to the page via Script Editor webpart, Content Editor Webpart with an external file as source, or any other way to bring a piece of HTML into the page.

Inside the form template, elements with attribute data-field define scope of the corresponding field. You can also put several field names into one data-field attribute, separated by whitespace (see tabs example below). Fields that are not specifically mentioned anywhere, will be rendered using data-field="*" template.

Notice: Internal field name should be used to identify fields.

Element with attribute data-role="field-control" will be replaced with the field control. The field control is rendered by CSR, so if you want to replace it with your custom control, you can do that using normal CSR approach.

Text in curly brackets will be evaluated against the corresponding field object, which looks like this:

JavaScript
{
  Description: "",
  Direction: "none",
  FieldType: "Text",
  Hidden: false,
  IMEMode: null,
  Id: "FIELD-GUID-GOES-HERE",
  MaxLength: 255,
  Name: "Title",
  ReadOnlyField: false,
  Required: true,
  Title: "Title",
  Type: "Text",
  Value: "Tallin"
}

So you can refer to any of those properties inside curly brackets. Depending on the field type, there may be some additional properties in this object (or some properties may not be there).

Why Not Render the Form Completely From Scratch?

Comparing to rendering the form from scratch, this approach has some important benefits:

  • Native SharePoint form functionality is retained (loading form data, saving, validation, ribbon integration, etc.)
  • This method is CSR-compatible: you can use CSR for customizing field controls as before. The code just adds ability to change forms layout
  • No external dependencies. Just copy-paste and it works.
  • Extremely small amount of code.

Another Example: Tabs

Let's say we have a standard Contacts list. Now it's extremely easy to add tabs to Contacts list form.

CSS + template code + jquery ui dependencies:

HTML
<div data-role="form" style="display:none">
    <div id="my-formtabs">
        <ul>
            <li><a href="#tab-general">General</a></li>
            <li><a href="#tab-contact-info">Contact info</a></li>
            <li><a href="#tab-other">Other</a></li>
        </ul>
        <div id="tab-general">
            <div data-field="Title FirstName Company JobTitle" 
            class="my-formfield">
                <label>{Title} <span style="{ Required ? 
                '' : 'display: none' }">*</span></label>
                <span data-role="field-control" />
            </div>
        </div>
        <div id="tab-contact-info">
            <div data-field="Email CellPhone 
            WorkAddress WorkState WorkCountry" class="my-formfield">
                <label>{Title} <span style="{ Required ? 
                '' : 'display: none' }">*</span></label>
                <span data-role="field-control" />
            </div>
        </div>
        <div id="tab-other">
            <div data-field="*" class="my-formfield">
                <label>{Title} <span style="{ Required ? '' : 
                'display: none' }">*</span></label>
                <span data-role="field-control" />
            </div>
        </div>
    </div>
</div>

<!-- CSS -->
<style>
.my-formfield {
   padding-bottom: 4px;
}
.my-formfield > label {
    font-size: 14px;
    width: 115px;
    display: inline-block;
    vertical-align: top;
}
.my-formfield > span {
    display: inline-block;
    vertical-align: top;
}
</style>

<!-- Jquery & Jquery UI -->

<link rel="stylesheet" 
href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script type="text/javascript">
  document.write('<script 
  src="//code.jquery.com/jquery-1.10.2.js"><'+'/script>');
  document.write('<script 
  src="//code.jquery.com/ui/1.11.4/jquery-ui.js"><'+'/script>');
</script>

<script type="text/javascript">
  $('#my-formtabs').tabs();
</script>

Notice: Of course, if you have MDS-enabled site, you shouldn't use inline code.

The result:

Image 3

Further Reading

Check out my other articles about CSR:

Conclusion

CSR is not perfect, but... it's JavaScript! So even if it doesn't support something, it's always possible to extend it and resolve the problem. 50 lines of code presented in this article enable custom layouts for CSR list forms.

License

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