Ingredients Custom Field

Custom Field Rendering with JSLink

A long time knock on SharePoint has been the lack of complex field types. For example, the ability to have cascading dropdowns when setting an item’s field has been highly requested. Many used custom field types to accomplish this in SharePoint 2010. If you’ve ever tried it…I apologize on behalf of Microsoft. It’s a fairly complex task that comes with a set of administrative issues that many (including myself) didn’t want to touch. But then came SharePoint 2013 and JSLink…

In a nutshell

JSLink is a property of a field. You can use this property to override how the field renders and how its value is set. In the example I have, I create a site column based on a Multiline text field. But when I set the JSLink property, I pretty much tell SharePoint “Hey, instead of rendering this field as a text box, use this JavaScript file to render the column.” With so much control, the possibilities are pretty much endless.

Credit

I followed a few different articles and blogs to get my sample working. The most helpful was definitely David Mann’s blog post. Next in line were http://msdn.microsoft.com/en-us/library/jj163799.aspx and http://msdn.microsoft.com/en-us/library/jj220061.aspx. I kind of mashed all three together to make my script. Hopefully David won’t mind but I’m going to follow his blog post’s layout but our code is quite different. Also thanks to Andrew Connell for letting me know about JSLink.

How to make it work

In order to override the field rendering the JavaScript file you write has to have at least 4 or 5 functions:

  • ‘View’ function to render the field if it is included in a list view. In this function you return html.
  • ‘DisplayForm’ function to render the field when the user is viewing the properties of a list item. Also returns html.
  • ‘EditForm’ function to render the field when the user is editing the properties of a list item. Returns html.
  • ‘NewForm’ function to render the field when the user is adding a new list item. Again, should return html. Normally this would be the same as the ‘EditForm’ but it doesn’t have to be.
  • ‘GetValueCallBack’ function to pass the field’s value to SharePoint after the user clicks Submit. This one should return the value type of the field type. In my example, I’ll return a string value.

The first step is to determine how you want the user to enter data. In the EditForm and NewForm functions, you’ll need to render the DOM elements to capture the data. This could be the cascading dropdowns or (in my example) several text boxes to capture sections of a single ingredient. You may also want to add JavaScript events to those elements.

Next determine how you want your value passed back to SharePoint. There are a few options to handle this. One option is to render a hidden input element when you render your data capture. Then handle events on the data capture elements (e.g. onchange). Within the event handlers populate the hidden input field’s value. In the end, pass the hidden input’s value to SharePoint in the getValueCallBack function.

My favorite option is a little easier to explain. Simply render your data capture elements. Handle onclick or onchange events with functions. Use those functions to create new DOM elements with the data within them. Then within the getValueCallBack function, get the values from the DOM elements. This is how my example is done. I chose this route because it gets difficult to keep the DOM and the hidden input in synch when users can delete items. That’ll make more sense in a

Recipe Ingredients

In this sample I created a field to contain ingredients for a recipe. I want the user to be able to enter several ingredients but have those values stored in one SharePoint field. In the end it looks a little something like this:

Edit and New Form

ingredients_edit

Display Form

ingredients_display

View

ingredients_view1
Ingredients View 2

Deep Dive

To kick things off I created a Sandbox solution with an empty elements file for my custom field.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field
ID="{D0A67455-6EBE-4665-9085-485009F0FFB3}"
Name="RecipeIngredients"
DisplayName="Ingredients"
Type="Note"
Required="TRUE"
JSLink="~site/style library/sample.js"
Group="Custom Columns"
Sealed="FALSE"
NumLines="1000"
RichText="FALSE"
RichTextMode="Text"
>
</Field>
</Elements>

Next I create an empty module to save my JavaScript file to the Style Library.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="Scripts" Url="style library">
<File Path="Scripts\Sample.js" Url="Sample.js" />
</Module>
</Elements>

I then added my JavaScript file to the module.

The Code

Near the top of the JavaScript file I create a function that will fire when the file is loaded. Within the function I associate my custom field with the functions that will render it. I then register that template to make SharePoint aware of it.

(function () {
    if (typeof SPClientTemplates === 'undefined')
        return;

    var ingredientsCtx = {};

    ingredientsCtx.Templates = {};
    //associate the various templates with rendering functions for our field.
    //when a list view is returned to the user, SharePoint will fire the function associate with 'View'.
    //when a list item is in New, SharePoint will fire the function associated with NewForm, etc.
    ingredientsCtx.Templates.Fields = {
        //RecipeIngredients is the Name of our field
        'RecipeIngredients': {
            'View': ingredientsView,
            'DisplayForm': ingredientsDisplayForm,
            'EditForm': ingredientsNewAndEdit, //using the same function for New and Edit, but they could be different
            'NewForm': ingredientsNewAndEdit
        }
    };

    //register the template to render our field
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(ingredientsCtx);

})();

The Code: Callbacks

There are several callbacks that you can listen to within your script. The most important one is the GetValueCallBack. As my comments state, this is where the magic happens! Oh you don’t believe in magic? Well this is where the field value get’s passed to SharePoint.


//registers call back functions from SharePoint
function RegisterCallBacks(formCtx) {

//when the form is initialized, call our anonymous function.
formCtx.registerInitCallback(formCtx.fieldName, function () {

//get the controls in the input form
var qtyInput = document.getElementById(ingredientQty);
var unitInput = document.getElementById(ingredientUnit);
var descInput = document.getElementById(ingredientDescription);
var prodIdInput = document.getElementById(ingredientProductId);
//add all of them to an array
var elements = [qtyInput, unitInput, descInput, prodIdInput];

//foreach element, register a keydown event to add an ingredient when the user hits enter
for (var i = 0; i < elements.length; i++) {
var input = elements[i];
if (input != null) {
AddEvtHandler(input, "onkeydown", function (e) {
//keyCode == 13 is Enter
if (e.keyCode == 13) {
addIngredient();
}
});
}
}
});

//This is where the magic happens! After the user clicks save, call this function. In this function, set the item field value.
formCtx.registerGetValueCallback(formCtx.fieldName, function () {
//get our unordered list of current ingredients
var ul = document.getElementById(ingredientsContainer);
if (ul == null)
return null;
else {
//return the values, which will be stored in the list item
return getFieldValueFromDOM(ul);
}

});

//create container for various validators
var validators = new SPClientForms.ClientValidation.ValidatorSet();

//if the field is required, make sure we handle that
if (formCtx.fieldSchema.Required) {
//add a required field validator to the collection of validators
validators.RegisterValidator(new SPClientForms.ClientValidation.RequiredValidator());
}

//if we have any validators, register those
if (validators._registeredValidators.length > 0) {
formCtx.registerClientValidator(formCtx.fieldName, validators);
}

//when there's a validation error, call this function
formCtx.registerValidationErrorCallback(formCtx.fieldName, function (errorResult) {
SPFormControl_AppendValidationErrorMessage(ingredientsContainer, errorResult);
});
}

The Code: New and Edit Forms

The meat of the rendering is handled by some helper functions I created that generate my html. In the RenderInputFields() function I render an ‘Add’ button with an onclick function of addIngredient(). When a user clicks ‘Add’ I take the user’s values, do some custom formatting, and add an LI element to the DOM.

//function called when an item with our field is in edit mode or new mode.
function ingredientsNewAndEdit(ctx) {
if (ctx == null || ctx.CurrentFieldValue == null)
return '';

var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
if (formCtx == null || formCtx.fieldSchema == null)
return '';

//register callback functions that SharePoint will call at appropriate times
RegisterCallBacks(formCtx);

//render the Input controls for the ingredient
var html = RenderInputFields();

//render a reminder for user experience
html += '<b>Current Ingredients:</b>';

//render existing values
html += RenderExistingValues(ctx, true);

return html;
}

//render the controls that allow users to add individual ingredients
function RenderInputFields() {
var html = '<table>';
html += '<tr><td>Qty</td><td>Unit of Measure</td><td>Description</td><td>Product Id</td><td></td></tr>';
html += '<tr>';
html += '<td><input id="' + ingredientQty + '" type="text"></td>';
html += '<td><select id="' + ingredientUnit + '">';
html += '<option value=""></option>';
html += '<option value="cup">Cup</option>';
html += '<option value="gallon">Gallon</option>';
html += '<option value="oz">Oz</option>';
html += '<option value="pinch">Pinch</option>';
html += '<option value="pt">Pt</option>';
html += '<option value="lb">Lb</option>';
html += '</select></td>';
html += '<td><input id="' + ingredientDescription + '" type="text"></td>';
html += '<td><input id="' + ingredientProductId + '" type="text"></td>';
//add a button with an onclick event for addIngredient. this will add a li element to the ingredients container UL
html += '<td><input id="btnAdd" type="button" onclick="addIngredient()" value="Add"></td>';
html += '</tr></table>';

return html;
}

//render the value from the current item
function RenderExistingValues(ctx, includeDelete) {
var html = '';
html += '<form action="javascript:return;">';
html += '<ul id="' + ingredientsContainer + '">';
//call a helper function to retrieve the fields value
var fieldValue = getValue(ctx);

var ingredients = fieldValue.split(ingredientDelimiter);

for (var i = 0; i < ingredients.length; i++) {
var ingredient = ingredients[i];
var qty = getAttributeFromFieldValue('qty', ingredient);
var unit = getAttributeFromFieldValue('unit', ingredient);
var desc = getAttributeFromFieldValue('desc', ingredient);
var prodId = getAttributeFromFieldValue('prodId', ingredient);

if (ingredient != '') {
html += '<li>';
html += getLIInnerHtml(qty, unit, desc, prodId, includeDelete);
html += '</li>';
}
}

html += '</ul></form>';

return html;
}

The Code: View

This function was a little fun. When the field is shown on a view I render a ‘Show Value’ button (stole that idea from David). I also render a hidden DIV that contains an unordered list of current ingredients which is generated by RenderExistingValues. When the user clicks the button, I open a SharePoint dialog to display the contents of that DIV.

//function called when our field is shown in a View
function ingredientsView(ctx) {
var currentVal = '';
//from the context get the current item and it's value
if (ctx != null && ctx.CurrentItem != null)
currentVal = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];

var currentItemValueId = 'ingredientValue_' + ctx.CurrentItem['ID'];

//create a hidden div to store the current item's value within the View
var html = '<div id="' + currentItemValueId + '" style="display:none">';
html += RenderExistingValues(ctx, false);
html += '</div>';
//render a 'Show Me' button. When clicked the value from the Div above will be cloned, then shown in dialog window
html += '<input type="button" value="Show Value" onclick="showIngredientsValue(\'' + currentItemValueId + '\')" />';

return html;
}

//opens a sharepoint dialog window to display value
function showIngredientsValue(ingredientsDisplayDivId) {
//calling the showModalDialog function with a DOM element.
//by default, that DoM element is destroyed when the dialog closes so
//we must clone the div we want to show. Otherwise the dialog would only work once

//establish a clone id
var cloneDiv = ingredientsDisplayDivId + '_clone';

//get the div with the value
var divWithValue = document.getElementById(ingredientsDisplayDivId);

//create a clone DOM element
var clone = document.createElement('DIV');

divWithValue.appendChild(clone);

//use the same innerhtml for the clone
clone.innerHTML = divWithValue.innerHTML;

SP.UI.ModalDialog.showModalDialog({
title: 'Current Ingredients',
html: clone, //pass in the clone which can be destroyed. Next time the function is called, we'll create another clone.
width: 450,
height: 350
});
}

The Code: Display Form

This uses the RenderExistingValues() function to generate an unordered list. The RenderExistingValues() function is used by the display form, the view form, and the edit/add forms.


//opens a sharepoint dialog window to display value
function showIngredientsValue(ingredientsDisplayDivId) {
//calling the showModalDialog function with a DOM element.
//by default, that DoM element is destroyed when the dialog closes so
//we must clone the div we want to show. Otherwise the dialog would only work once

//establish a clone id
var cloneDiv = ingredientsDisplayDivId + '_clone';

//get the div with the value
var divWithValue = document.getElementById(ingredientsDisplayDivId);

//create a clone DOM element
var clone = document.createElement('DIV');

divWithValue.appendChild(clone);

//use the same innerhtml for the clone
clone.innerHTML = divWithValue.innerHTML;

SP.UI.ModalDialog.showModalDialog({
title: 'Current Ingredients',
html: clone, //pass in the clone which can be destroyed. Next time the function is called, we'll create another clone.
width: 450,
height: 350
});
}

Wrapping Up

So that’s about it. Feel free to download the solution. The Sample.js file has about 350 lines of code but I have at least 100 lines of comments and several blank lines. Hopefully you’ll find it useful.

Here’s the Visual Studio project -> CustomFields.zip

16 thoughts on “Custom Field Rendering with JSLink

  1. Pingback: SharePoint Development Lab by @avishnyakov » Custom Field Type for SharePoint 2013 – VISA card field sample

  2. Pingback: SharePoint Development Lab by @avishnyakov » Custom Field Type for SharePoint 2013 – Custom “Quick Edit” mode rendering

  3. Pingback: SharePoint Development Lab by @avishnyakov » I am speaking at Darwing SharePoint user group – Introduction to client side rendering in SharePoint 2013

  4. dealkk

    i don’t see where you set jslink? Do you have to do it on list view, form view, display view…?

    Reply
    1. Rob

      Lester, have you tried using JSLink on external lists? I can’t find anything that says it’s prohibited, but I’m having a devil of a time trying to get it to work.

      Reply
  5. Gennaro

    Dear Lester,

    I have this problem: I have created a custom field, (a LookupField), and I have also created the .js file to render the control for the view form, (AllItems), of the list where the control is used.

    In the default view of the custom list where I have my custom field, all works fine, but I also have a page where I have another view of the same list to show only certain items: in this view it seems that the .js file is not used. The control is not rendered and I see something like

    BLA BLA BLA

    can you help me?

    Thank you very much,
    Gennaro.

    Reply
  6. Gennaro

    Ops I am sorry.

    What I see is something like:

    &lta onclick=”OpenPopUpPage(‘http://mysite/siteCollection/_layouts/15/listform.aspx?PageType=4&ListId=25394913-1899-449b-8947-3ef7fdfa5cd1&ID=541&RootFolder=*’, RefreshPage); return false;” href=”http://mysite/siteCollection/_layouts/15/listform.aspx?PageType=4&ListId=25394913-1899-449b-8947-3ef7fdfa5cd1&ID=541&RootFolder=*”&gtBLA BLA BLA&lt/a&gt

    Reply
    1. LesterLester Post author

      How did you apply the JSLink property? Did you add it to the ListViewWebPart on the AllItems page? Or to the View? I think this can be solved by setting the JSLink property of the ListViewWebPart on the other page. This isn’t very convenient so for this very reason, my strong preference is the set the JSLink property of the Field itself. Then you don’t have to worry about what page, view, or list your field is used with. It’ll just work.

      Reply
      1. Aackose

        Great Post!

        I seem to end up with the same issue. I have the 3 lists rendered in 3 different ways on the same page (Default.aspx) using 3 list view webparts each with its own JsLink to modify the views. ex. one displays the list with accordion functionality, the other displays the list as a mega menu and the 3rd one displays the list items in a color coded manner.

        When i load all the 3 list views in the same page (although the JsLinks are different), the first and the second one works fine, but the last one just renders based on the JsLink functionality written for the second. Although ideally all of them should be able to coexist on the same page.

        Have you faced any such issue with multiple list views on the same page, using different JsLinks?

        Reply
  7. Pingback: SharePoint Custom Field Rendering with JSLink | Share your knowledge

  8. Pingback: registerGetValueCallback sharepoint js links | Share your knowledge

  9. Pingback: SharePoint | Bits and Bytes » SharePoint 2013 Quick Edit JavaScript Validation

  10. George Nolan

    You mentioned Cascading dropdown list MANY times, I was expected you implementing a REAL cascading list, but this doesn’t have to do anything with cascading, this post was totally misleading!

    Reply
    1. LesterLester Post author

      Hi George. This post is designed to show you how to use JavaScript to render a field any way you want, hence the title “Custom Field Rendering with JSLink.” I’m hoping that after reading this post you can see how to do this on your own using JavaScript. You can find several cascading drop down JavaScript plugins on the web. It wouldn’t be overly difficult to implement using those steps.

      Reply

Leave a Reply to George Nolan Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>