My original approach to implementing this kind of thing was to add a column to the list as a place-holder for where my dynamic value would be rendered, and then link the CSR override script to that field using the field's JSLink property.
However, at the recent SharePointFest DC conference, I was at a talk about CSR, and it was mentioned that, from a data integrity standpoint, that is a bad practice because you are essentially adding a field to the list in which you are never going to store any data. It was also mentioned that you could probably just add a field directly in the view you do your rendering on.
I thought that sounded reasonable, and was an interesting approach, so I played around with the idea for a little bit and came up with this general pattern for doing just that.
And instead of attaching to the JSLink property of a field, these scripts are intended to be added directly to the list Views through the List View Web Part's JSLink property (which is exposed in the web part's Editor pane).
And instead of attaching to the JSLink property of a field, these scripts are intended to be added directly to the list Views through the List View Web Part's JSLink property (which is exposed in the web part's Editor pane).
During my experimentation, I was trying to add the temporary column in
OnPreRender
, but still register a field override for that field in the normal manner. That turned out to be problematic, so eventually I settled on using OnPreRender
to add the temporary field to the view, and using OnPostRender
to do all the calculations and render the final HTML.
Here's a code snippet that outlines the basic pattern, which I have commented heavily so you can follow along:
- var DEC = DEC || {};
- DEC.FakeField = (function () {
- function addTemporaryField(ctx) {
- // make sure we haven't added the field already,
- // in case OnPreRender fires twice, which it does sometimes
- if (ctx.ListSchema.Field.filter(function (fld) {
- return fld.Name == 'FakeField';
- }).length == 0) {
- // create the field schema object to insert
- var FakeFieldObj = {
- AllowGridEditing: 'FALSE',
- DisplayName: 'Fake Field',
- RealFieldName: 'FakeField',
- Name: 'FakeField',
- FieldType: 'Text',
- Type: 'Text',
- Filterable: 'FALSE',
- Sortable: 'FALSE',
- ReadOnly: 'TRUE',
- };
- // find the index of the field to insert next to,
- // based on that field's DISPLAY name.
- var insertIdx = ctx.ListSchema.Field.map(function (fld) {
- return fld.DisplayName;
- }).indexOf('Some Field Name');
- // if you want to insert *before* the field do not add anything
- // but, if you want to insert *after* the field, add 1
- insertIdx++;
- // insert your fake field schema object into the list schema
- ctx.ListSchema.Field.splice(insertIdx, 0, FakeFieldObj);
- }
- }
- function renderFakeFieldValues(ctx) {
- // get the index of the fake field column in the table
- // by looking for the header with the DISPLAY NAME of our fake field,
- // so we know where to insert the final rendered value for each item
- var colIndex = 0;
- var headers = document.querySelectorAll('.ms-listviewtable th');
- var len = headers.length;
- for (var idx = 0; idx < len; idx++) {
- if (headers[idx].innerHTML.indexOf('Fake Field') != -1) {
- colIndex = idx;
- }
- };
- // go through the rows and do whatever calculations we need
- // to get the value we want to render in the added field.
- // at this point ctx.ListData.Row represents a collection
- // of list items for all the rows currently visible in the view.
- ctx.ListData.Row.forEach(function (listItem) {
- // here, listItem will have all the data of all
- // the columns visible in the view, which we
- // can access by listItem.FieldInternalName
- // we can then use those values to do our conditional formatting
- // and calculate / generate the value to render in the fake field
- var helloWorldHtml = '<span>Hello ' + listItem.WorldField + '</span>';
- // find the row in the table that corresponds to this list item
- var iid = GenerateIIDForListItem(ctx, listItem);
- var row = document.getElementById(iid);
- // insert the value we want in the fake field column
- // of the row representing this list item
- row.children[colIndex].innerHTML = helloWorldHtml;
- });
- }
- return {
- render: function () {
- SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
- OnPreRender: addTemporaryField,
- OnPostRender: renderFakeFieldValues
- });
- }
- }
- })();
- RegisterModuleInit(SPClientTemplates.Utility.ReplaceUrlTokens("~site/SiteAssets/Scripts/AddFakeField.js"), DEC.FakeField.render);
- DEC.FakeField.render();
So where is this useful? Well, let's say you want to highlight list items that are overdue. Sure, you could come up with a rendering override for the due date field itself, and either render the text in red, or add a red background, or something like that. But what if you wanted to display a message to the users that spelled out exactly how many days overdue the item was, like "XX days overdue".
The following code snippet shows how to do exactly that:
- var DEC = DEC || {};
- DEC.DaysOverdueField = (function () {
- function addTemporaryField(ctx) {
- if (ctx.ListSchema.Field.filter(function (fld) {
- return fld.Name == 'DaysOverdueField';
- }).length == 0) {
- var daysOverdueFieldObj = {
- AllowGridEditing: 'FALSE',
- DisplayName: 'Days Overdue',
- RealFieldName: 'DaysOverdueField',
- Name: 'DaysOverdueField',
- FieldType: 'Text',
- Type: 'Text',
- Filterable: 'FALSE',
- Sortable: 'FALSE',
- ReadOnly: 'TRUE',
- };
- // in this case i am going to show the "XX days overdue"
- // message directly next to the due date field
- var insertIdx = ctx.ListSchema.Field.map(function (fld) {
- return fld.DisplayName;
- }).indexOf('Due Date');
- // insert *after* the due date field
- insertIdx++;
- ctx.ListSchema.Field.splice(insertIdx, 0, daysOverdueFieldObj);
- }
- }
- function calculateOverdueDays(ctx) {
- // get the index of the "Days Overdue" column in the table
- var colIndex = 0;
- var headers = document.querySelectorAll('.ms-listviewtable th');
- var len = headers.length;
- for (var idx = 0; idx < len; idx++) {
- if (headers[idx].innerHTML.indexOf('Days Overdue') != -1) {
- colIndex = idx;
- }
- };
- // go through the rows and display an overdue message if it is overdue
- ctx.ListData.Row.forEach(function (listItem) {
- // get the due date
- var duedate = new Date(listItem.DueDate);
- // use the built in GetDaysAfterToday function to calculate
- // how many days overdue an item is. if it is truly overdue,
- // this function will return a negative number.
- var overdueDays = GetDaysAfterToday(duedate);
- if (overdueDays < 0) {
- // change the negative to a positive
- overdueDays = Math.abs(overdueDays);
- // find the row in the table that corresponds to this list item
- var iid = GenerateIIDForListItem(ctx, listItem);
- var row = document.getElementById(iid);
- // insert the overdue message in the <td> element in our temporary "Days Overdue" column
- row.children[colIndex].innerHTML = '<span style="color:red;">' + overdueDays + ' days overdue</span>';
- }
- });
- }
- return {
- render: function () {
- SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
- OnPreRender: addTemporaryField,
- OnPostRender: calculateOverdueDays
- });
- }
- }
- })();
- RegisterModuleInit(SPClientTemplates.Utility.ReplaceUrlTokens("~site/SiteAssets/Scripts/ShowDaysOverdue.js"), DEC.DaysOverdueField.render);
- DEC.DaysOverdueField.render();
So, that is how you can add a temporary column to a list view, and then access it to add dynamic information related to the list items into the view. As requested, here is a screen shot of the "days overdue" code in action. The column "Days Overdue" does not exist on this list:
In a subsequent post I will show how to use this technique to do something a little more complex: display file counts next to folders in a Document Library view that includes all files within all subfolders within any particular folder.
In a subsequent post I will show how to use this technique to do something a little more complex: display file counts next to folders in a Document Library view that includes all files within all subfolders within any particular folder.