17

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

2
  • 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, 2015 at 6:41
  • great question... helped me a lot Feb 27, 2015 at 13:47

6 Answers 6

23

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
  • 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, 2014 at 6:56
  • 2
    Great, regarding documentation, i am also not aware any specific documentation and usually dig in clientforms.debug.js ;) Aug 21, 2014 at 8:47
32
                    '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
                    }
4
  • 3
    Those information are pure gold. Can I ask where you found that out? Reverse-engineering or you actually found that documented somewhere??
    – SPArcheon
    May 20, 2015 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, 2015 at 6:04
  • 5
    I know it has been awhile, but can anybody explain how to use this?
    – Rothrock
    Mar 31, 2017 at 21:52
  • 1
    @Rothrock and anyone else:those are function names SP uses to render the default HTML/controls for those field types. So if you were overriding a date field, for instance, and you wanted to get the default date picker control but just wrap it with some custom HTML, in your override function you could do var defaultHtml = SPFieldDateTime_Edit(ctx); to get the default HTML, use that however you want, and then return your final HTML back out to the rendering engine (same as usual override behavior). Just use this list to look up the right function name for the field type you are overriding. Jun 5, 2019 at 19:30
11

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);
}
2
  • Can you tell me what the XSLRender flag stands for?
    – Emaborsa
    Aug 22, 2018 at 11:13
  • What is 'field'?..
    – Gennady G
    Apr 2, 2019 at 12:54
4

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.

4
  • My field, listItem and listSchema are not initialized (im testing this in NewForm)
    – Hybris95
    Jul 18, 2016 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, 2016 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, 2017 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, 2017 at 9:12
2

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.

2

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.

2
  • 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, 2017 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, 2018 at 18:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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