سؤال

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

هل كانت مفيدة؟

المحلول

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

نصائح أخرى

                    '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
                    }

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);
}

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.

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.

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى sharepoint.stackexchange
scroll top