I got a very strange problem.

I need to return default rendering for UserField (PeoplePicker) for some SP users with current user, and show read-only field with current user for others. In other words, only admins should have rights to choose people, others should be forced to use current name.

Read-only UserField is not a problem: http://code.msdn.microsoft.com/office/Sample-8-List-add-and-edit-d228b751

But to return default rendering in JSLink is a tough one. Is there any any easy ways? Or should I use magic of onPostRender() method?

I found this:

ctx.CurrentFieldSchema.DefaultRender = true;

But it's doing nothing

  • This is bad practice. Just because you are hiding the people picker doesn't mean that you are securing the list itself. – Christophe Feb 7 '15 at 6:41
  • great question... helped me a lot – Kannan Feb 27 '15 at 13:47
up vote 22 down vote accepted

The following templates define how User field is rendered in List Form pages.

Single-valued user field:

  • New: SPClientPeoplePickerCSRTemplate
  • Edit: SPClientPeoplePickerCSRTemplate
  • Display: SPFieldUser_Display

Multi-valued user field:

  • New: SPClientPeoplePickerCSRTemplate
  • Edit: SPClientPeoplePickerCSRTemplate
  • Display: SPFieldUserMulti_Display

JavaScript Template

Assume a Tasks list that contains a Task Category field. Depending whether the value is set to Internal or not, AssignedTo field have to be rendered as a standard editable or ReadOnly control. The following example demonstrates how to achieve that:

(function () {
    var ctx = {};
    ctx.Templates = {};
    ctx.Templates.Fields = {
        'AssignedTo': {
            'EditForm': renderAssignedTo
        }
    };
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(ctx);
})();


function renderAssignedTo(ctx) {
    var readOnly = (ctx.CurrentItem.TaskCategory == "1;#Internal");      
    if(readOnly) {
       prepareUserFieldValue(ctx);  
       return SPFieldUserMulti_Display(ctx);
     }  
     return SPClientPeoplePickerCSRTemplate(ctx);
}

function prepareUserFieldValue(ctx) { 
    var item = ctx['CurrentItem']; 
    var userField = item[ctx.CurrentFieldSchema.Name]; 
    var fieldValue = ""; 

    for (var i = 0; i < userField.length; i++) { 
        fieldValue += userField[i].EntityData.SPUserID + SPClientTemplates.Utility.UserLookupDelimitString + userField[i].DisplayText; 

        if ((i + 1) != userField.length) { 
            fieldValue += SPClientTemplates.Utility.UserLookupDelimitString 
        } 
    } 

    ctx["CurrentFieldValue"] = fieldValue; 
}

Results

Fig. 1 Task Edit Form page with default AssignedTo field enter image description here

Fig. 2 Task Edit Form page with read-only AssignedTo field enter image description here

  • 2
    Yes, this works like a charm. Insanely helpful, thank you! Btw, do you know any place where I can find documentation on templates for every default SP field? – Михаил Андреев Aug 21 '14 at 6:56
  • 2
    Great, regarding documentation, i am also not aware any specific documentation and usually dig in clientforms.debug.js ;) – Vadim Gremyachev Aug 21 '14 at 8:47
                    'Text': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPFieldText_Edit,
                        'NewForm': SPFieldText_Edit
                    },
                    'Number': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPFieldNumber_Edit,
                        'NewForm': SPFieldNumber_Edit
                    },
                    'Integer': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPFieldNumber_Edit,
                        'NewForm': SPFieldNumber_Edit
                    },
                    'Boolean': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_DefaultNoEncode,
                        'EditForm': SPFieldBoolean_Edit,
                        'NewForm': SPFieldBoolean_Edit
                    },
                    'Note': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldNote_Display,
                        'EditForm': SPFieldNote_Edit,
                        'NewForm': SPFieldNote_Edit
                    },
                    'Currency': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPFieldNumber_Edit,
                        'NewForm': SPFieldNumber_Edit
                    },
                    'File': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldFile_Display,
                        'EditForm': SPFieldFile_Edit,
                        'NewForm': SPFieldFile_Edit
                    },
                    'Calculated': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPField_FormDisplay_Empty,
                        'NewForm': SPField_FormDisplay_Empty
                    },
                    'Choice': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPFieldChoice_Edit,
                        'NewForm': SPFieldChoice_Edit
                    },
                    'MultiChoice': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPFieldMultiChoice_Edit,
                        'NewForm': SPFieldMultiChoice_Edit
                    },
                    'Lookup': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldLookup_Display,
                        'EditForm': SPFieldLookup_Edit,
                        'NewForm': SPFieldLookup_Edit
                    },
                    'LookupMulti': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldLookup_Display,
                        'EditForm': SPFieldLookup_Edit,
                        'NewForm': SPFieldLookup_Edit
                    },
                    'Computed': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPField_FormDisplay_Default,
                        'EditForm': SPField_FormDisplay_Default,
                        'NewForm': SPField_FormDisplay_Default
                    },
                    'URL': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldUrl_Display,
                        'EditForm': SPFieldUrl_Edit,
                        'NewForm': SPFieldUrl_Edit
                    },
                    'User': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldUser_Display,
                        'EditForm': SPClientPeoplePickerCSRTemplate,
                        'NewForm': SPClientPeoplePickerCSRTemplate
                    },
                    'UserMulti': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldUserMulti_Display,
                        'EditForm': SPClientPeoplePickerCSRTemplate,
                        'NewForm': SPClientPeoplePickerCSRTemplate
                    },
                    'DateTime': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldDateTime_Display,
                        'EditForm': SPFieldDateTime_Edit,
                        'NewForm': SPFieldDateTime_Edit
                    },
                    'Attachments': {
                        'View': RenderFieldValueDefault,
                        'DisplayForm': SPFieldAttachments_Default,
                        'EditForm': SPFieldAttachments_Default,
                        'NewForm': SPFieldAttachments_Default
                    }
  • 3
    Those information are pure gold. Can I ask where you found that out? Reverse-engineering or you actually found that documented somewhere?? – SPArchaeologist May 20 '15 at 12:20
  • 1
    Use the following for TaxonomyFieldType: Display: SP.UI.Taxonomy.TaxonomyFieldTemplate.renderDisplayControl(ctx); Edit: SP.UI.Taxonomy.TaxonomyFieldTemplate.renderFormControl(ctx); – David May 22 '15 at 6:04
  • 3
    I know it has been awhile, but can anybody explain how to use this? – Rothrock Mar 31 '17 at 21:52

If you look in the clienttemplates.debug.js file you'll see what SharePoint does internally to render fields. It has a "map" class that maps the field.FieldType (or field.Type) to the appropriate field renderer function. You can copy that map into your own function and get back the right renderer for any occasion:

// Returns the HTML that would have been rendered for a field if no custom rendering template were applied.
function getDefaultFieldHtml(renderCtx, field, listItem, listSchema) {
    var renderingTemplateToUse = null;

    var fieldRenderMap = {
        Computed: new ComputedFieldRenderer(field.Name),
        Attachments: new AttachmentFieldRenderer(field.Name),
        User: new UserFieldRenderer(field.Name),
        UserMulti: new UserFieldRenderer(field.Name),
        URL: new UrlFieldRenderer(field.Name),
        Note: new NoteFieldRenderer(field.Name),
        Recurrence: new RecurrenceFieldRenderer(field.Name),
        CrossProjectLink: new ProjectLinkFieldRenderer(field.Name),
        AllDayEvent: new AllDayEventFieldRenderer(field.Name),
        Number: new NumberFieldRenderer(field.Name),
        BusinessData: new BusinessDataFieldRenderer(field.Name),
        Currency: new NumberFieldRenderer(field.Name),
        DateTime: new DateTimeFieldRenderer(field.Name),
        Text: new TextFieldRenderer(field.Name),
        Lookup: new LookupFieldRenderer(field.Name),
        LookupMulti: new LookupFieldRenderer(field.Name),
        WorkflowStatus: new RawFieldRenderer(field.Name)
    };

    if (field.XSLRender == '1') {
        renderingTemplateToUse = new RawFieldRenderer(field.Name);
    }
    else {
        renderingTemplateToUse = fieldRenderMap[field.FieldType];
        if (renderingTemplateToUse == null)
            renderingTemplateToUse = fieldRenderMap[field.Type];
    }
    if (renderingTemplateToUse == null)
        renderingTemplateToUse = new FieldRenderer(field.Name);

    return renderingTemplateToUse.RenderField(renderCtx, field, listItem, listSchema);
}

Then to get the default HTML that would have been rendered, just call it like this:

if (isReadOnly) {
    // Do your thing
} else {
    return getDefaultFieldHtml(ctx, field, listItem, listSchema);
}
  • Can you tell me what the XSLRender flag stands for? – Emaborsa Aug 22 at 11:13

There's an even easier way. The SPMgr class can be instantiated, and then you can call the public method RenderItem on it. For that to work as expected (and not get caught in a loop), you need to clone two of the objects passed in to the custom field renderer function. jQuery extend is good for cloning. Here's an example:

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
    Templates: {
        Fields: {
            MyField: {
                View: function(ctx, field, listItem, listSchema) {
                    var fieldCopy = jQuery.extend(true, {}, field);
                    var ctxCopy = jQuery.extend(true, {}, ctx);
                    delete fieldCopy.fieldRenderer;
                    ctxCopy.Templates.Fields[field.Name] = null;
                    var spmgr = new SPMgr();
                    var output = spmgr.RenderField(ctxCopy, fieldCopy, listItem, listSchema);
                    // do whatever you need to do here
                    return output;
                }
            }
        }
    }
});

I think that's better and it's certainly cleaner than duplicating code in the clienttemplates.js library.

  • My field, listItem and listSchema are not initialized (im testing this in NewForm) – Hybris95 Jul 18 '16 at 11:08
  • 1
    Yes, this technique doesn't work quite the same way in a form context. It works in a View context. I'm sure there's a similar technique for form rendering. I haven't had a need to explore this. Given that the OP was about form rendering overrides, I guess my answer missed the mark. Sorry. To those suggesting the jQuery object isn't defined, it's whatever your jQuery root class is called. Some distributions with SharePoint might rename it. I use one from jquery.com. You can probably just use $, so $.extend. – flayman Jul 18 '16 at 16:08
  • I like how this is cleaner, but it does involve also having jQuery available. Can you explain why you need to clone the two objects? – Rothrock Mar 31 '17 at 23:19
  • Hi. If you don't clone the context and field arguments, then you would need to save a copy of the field.fieldRenderer method and the value of ctx.Templates.Fields[field.Name] and restore them at the end of this override function. That might work, but as I wasn't sure about what other threads might be accessing them I thought it safer to just clone those objects, particularly the context (ctx). There are other ways of creating copies of JavaScript objects. JQuery was easiest for me as I was already using it. – flayman Apr 3 '17 at 9:12

I needed something similar, but I needed it to work for any field, and on any form or view. flayman's answer got me there on views. This is what I came up with for the forms:

(function() {

    // this array is the only thing that needs to be modified to override more or different fields
    var fields = [
        "JobTitle",
        "WebPage",
        "Comments"
    ];

    passThroughOverride = function(ctx) {
        // get the default templates for each field type
        var templatesByType = SPClientTemplates._defaultTemplates.Fields.default.all.all;
        // get the default templates for the current field type
        var currentTemplates = templatesByType[ctx.CurrentFieldSchema.Type];
        // get the render function by view id (i.e. NewForm, View, etc.)
        var currentRenderFunc = currentTemplates[ctx.BaseViewID];
        // call the render function
        var result = currentRenderFunc(ctx);
        // do your own work here
        return result;
    }

    // create an empty overrides instance
    var overrides = {
        Templates: {
            'Fields': {}
        }
    };

    // add render overrides for each field we want to customize.
    for(var i=0; i<fields.length; i++) {
        var current = fields[i];
        overrides.Templates.Fields[current] = {
            'NewForm': passThroughOverride,
            'EditForm': passThroughOverride,
            'DisplayForm': passThroughOverride
        };
    }

    // also just register for full page loads (F5/refresh)
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrides);

})();

The array of field internal names at the top is the only thing that needs to be edited to apply this to different fields (currently setup to override a few field types in the Contacts list). This should work for any field type on any form. I tried to use it on views too, but the defaultTemplates structure doesn't exist on views.

  • This looks better than my technique as it provides direct access to the render function without the need for overriding or cloning the context object. Of course, something like SPClientTemplates._defaultTemplates.Fields.default.all.all is not documented anywhere (I'd be very surprised if I am mistaken) and so you have to be tenacious and lucky to find it. – flayman Apr 3 '17 at 9:18
  • Or both, but I'm not the one who tracked it down. I read about it in a Code Project article by Andrei Markeev codeproject.com/script/Membership/View.aspx?mid=7585543 – Joe McShea Jan 29 at 18:37

I found the answers to this question extremely useful in solving a similar CSR issue and just wanted to leave a link to my solution incase it helps anyone else.

I used 2 different approaches for manipulating the header and body templates.

One is using the default render to provide the html and then manipulating it using regex.

The other is 'hacking' the renderCtx object to remove a field that I wanted to hide and THEN calling the default render (and finally replacing what I removed back into the renderCtx.).

Do not apply CSR Override in QuickEdit Mode

@Jim Brown - thank you for the function that returns the default render !!!

@flayman - that code looked like it would work well but I had trouble getting it to work. Probably jQuery issues in my environment.

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.