An Entity Editor Display Template, Advanced Client Side Rendering

Ever had a list in SharePoint with a choice field that allowed multiple selections (i.e. checkboxes)? And with many things to choose from? See the picture below. In this particular case, you’d need to scroll down a page or two to see all of the choices. I’ve seen a lot of people try to solve this problem by making the choices wrap around inline instead of one per line, which is a fine solution if your choices are relatively small strings and there is a small enough number of them. But what if there are over 100 choices, and some of them are pretty big strings? That’s the problem I’m trying to solve with this Entity Editor Display Template.


Multiple Choice Field

So what is an entity editor? Think people picker. It provides an input where you can start typing the name of something and it will provide autocomplete with a known list of possible values (in this case the available choices of a choice field). You can then ‘resolve’ your choice, by arrowing up and down in the autocomplete, and clicking with the mouse or hitting enter when the right choice is selected. You can also hit enter when there are no autocomplete choices available, and it will either raise a validation if fill-in choices are not allowed in the field or add whatever you’ve typed as a resolved entity if they are allowed.

Like my last post, this solution will use pure JavaScript for DOM manipulation, but it does have a dependency. It uses a library call Awesomplete for the autocomplete functionality. It’s very light-weight (under 2k minimized) and provides a very nice autocomplete implementation. But as small as it is, it’s still twice the size of my entity editor CSR, so I don’t want to roll my own.

What are we Building?

I’ve already explained what we’re building; now I’m going to show it. Below is a picture of the same form I showed above, but the entity editor template has been applied to the 3 choice fields (of course, you only saw 2 of the choice fields in the previous image, because the second was so obnoxiously big that it pushed the 3rd way off the page).

Entity Editors in Form

The first choice is a single select, so it doesn’t technically require the entity editor template, but in for a penny, in for a pound as the saying goes. The entity editor template can be applied to any choice field (drop down, radio buttons, or checkboxes). That doesn’t mean that it will look different, it’s going to look the same for each, but it will behave a bit differently for single select vs. multiple select. The other two are both multiple select choice fields with a lot of choices. I think it’s fairly obvious that this form is a bit nicer than the previous one. If you don’t, get out of here already, this post isn’t for you!

If it’s a single select choice field (either drop down or radio buttons), the entity editor will let the user resolve one entity and then take away the cursor. There’s no way to enter more. If it’s multi-select, they can keep resolving entities to their heart’s content until they run out of choices. Or beyond that, if fill-in choices are allowed on the choice field.

Resolving the entity is just taking whatever is in the input and seeing if it uniquely resolves to a choice, or selecting an autocomplete choice, or taking whatever is in the input and adding it as a new resolved entity if fill-in choices are allowed. Resolution can only be attempted if the input control has text in it. And it can be initiated by either selecting from the drop down, hitting enter, or submitting the form. When the input is successfully resolved, a new entity (grey rounded box) is added and the input is cleared. If resolution is attempted, but the resolution is unsuccessful (i.e. user typed something, not in the choices, and fill-in choices not allowed), then a validation error is raised and displayed.

You can remove a resolved entity by clicking the little blue x inside of it. If it’s a multiple selection choice field, and the input is empty, you can also delete the last entity just by hitting the backspace key while the input has focus.

If you’ve been doing SharePoint for any significant period of time, the similarity to people picker should be pretty obvious. Change the styles just a little bit and it could look exactly like a people picker if that’s what you want.

The HTML and CSS

Now let’s break down the parts of the entity editor UI first, because it will make the code easier to understand:

Rendered HTML for an Entity Editor

The entity editor is just a div with some stuff in it. It has a grey border that changes color on hover, making it mimic other input fields in SharePoint. From now on, I’m just going to refer to this as entityEditor, because that’s also the name I use for it in all of my source code.

Inside that, there’s just a span for each resolved entity containing the actual entity text, and if in the new form or edit form, an anchor tag to delete the entity.

Finally, on new or edit forms, there is an input with no border. It having no border is important, as it makes it invisible except for a floating cursor, making it appear that the entityEditor is the input, which is what we want. It gets hidden on single select choice fields once an entity has been resolved. The input is consistently referenced in the code as entityEditorInput.

And below is the actual HTML produced. Nothing special here, except that I add data attributes to various nodes. This allows event handlers on those nodes to quickly tell which field they are working with. Also, note that the Awesomplete library wraps my input in a div and inserts a couple of other siblings for its own purposes. That’s only significant because some of the event handlers we’ll talk about later fire on the input, and they find the entity editor using parentNode.parentNode, which if you look at my methods for generating the DOM, that doesn’t look right (looks like it should be just parentNode), but since Awesomplete added a div it is right. Also, the last node in the outer div is a span that will display any validation errors.

Finally, below is the CSS, which like my previous examples, is embedded into the JavaScript and shoved inline into the DOM on pre-render in order to avoid FOUC (Flash of Unstyled Content). There’s nothing special here, almost all of it would have worked the same in browsers 10 years ago, except for border-radius. That wasn’t supported by older browsers, but the only result would be that the entities would appear more rectangular. Few people are using a browser that old these days, and if they are, screw them (I mean that in the nicest possible way of course).

The technique used here is pretty simple, and this kind of control could be used in a lot of web-based applications, not just SharePoint. In fact, I stole it from a PHP/MySQL blog post I read about 10 years ago, which I would have included in my references but I can’t find it anymore.

The Implementation

Hey, I’ve got an idea, let’s talk some JavaScript. Now what follows is going to be a whole lot of code, and very brief explanations, mostly because a lot of the code is just going to be rendering the DOM I described above, or handling events and manipulating the DOM elements in ways I’ve described above.

The Shell

This is the entire entity editor implementation, minus the parts with the TBD comments of course. This looks a lot like the shell for just about every other CSR template I’ve done in this series, so if you can’t figure it out, you might need to go back and read some of the earlier posts in this series. The rest of this post is going to describe those TBDs pretty much in the order in which they occur.

The render methods and any helpers they need are going to be entirely contained in the Object literal instance called impl. Every bit of source code I show for the remainder of this article will be fleshing out member methods and properties of that instance.

On Pre-render

The only role of the pre-render method in this example is to load CSS. My CSS is hard-coded in the JavaScript and shoved inline into the DOM, so it’s available before any of my DOM is rendered avoiding FOUC (as previously mentioned).

The pre-render method also inserts a style element into the head to load Awesomplete’s CSS from an external file. I could have just sucked this CSS into my JavaScript and loaded it inline too, but FOUC is less of an issue here because nothing that Awesomplete does is going to be visible until the user starts typing anyway, and if the CSS hasn’t loaded yet the page is probably pretty ugly and unresponsive anyway (i.e. it’s still SharePoint after all).

The property impl.cssLoaded is a boolean member property used as a flag to ensure that I don’t load the CSS more than once.

Rendering for Display Forms and Views

As usual, I have a single method for both display forms and views, because usually they need to do the same thing, or at least very nearly the same thing. Also as usual, I try not to render a bunch of HTML by concatenating a bunch of strings and variables. Instead, I create DOM nodes (either jQuery, or as in this case pure JavaScript) and manipulate them through the API. Literally, all this method is doing is creating a div. Then, if the current item has a non-empty value for our field, it splits it up if necessary, creates a span for each value, and shoves that span into the div. Finally, it returns the div source (outerHTML).

Rendering for New and Edit Views

I also generally use a single method for rendering fields on new and edit forms, since they generally render the same thing with the only difference being that the edit form may have to initialize the input value from the render context (actually, the new form may need to do this also, if you’ve configured a default value for this field). Here is that method:

Doesn’t look like much, but mostly it farms out the work to a bunch of helper methods in impl.

But first, it stores some field specific stuff in a map of field names to objects, in a property called impl.fieldMap. For instance, for the 3 fields my CSR is intended to intercept, I’ll end up with a mapping that looks like (condensed view):

So at the moment, the field map stores two properties, schema and source, both of which it gets from the rendering context. schema is the field schema for the field and contains a lot of useful stuff (like for choice fields, the available choices and whether fill-in choices are allowed). source is an array of available choices for this field, which will be the source for the Awesomplete type ahead. I store these things so they can later be used by callback functions like JavaScript event handlers, which can generally determine the current field name through data attributes, but aren’t passed the context.

Next I need to build the DOM elements that will render my control. That’s done by three helper methods shown below, appendOuterContainer (renders the entity editor div), appendInitialEntities (adds an entity span for each current value), and appendInput (adds the entity editor input). appendInitialEntities further farms out building the actual DOM nodes to appendEntity, which is also called later as various event callbacks resolve entities.

Next, the rendering method registers a get value callback, which ultimately calls impl.getFieldValue shown below. This method does a lot of the heavy lifting of trying to resolve entities from unresolved text in the input buffer, when the user tries to submit the form. If it finds an exact match ignoring case, it resolves to that and clears the input. If fill in choices are allowed and there is still text in the input, it resolves whatever is there and clears the input. If there is still text in the input once this function is done, then there is a validation error, so it really does the heavy lifting for validation too.

Finally the render method calls impl.registerValidators. This renders one custom validator, which checks if the field does not allow fill-in choices and if there is still text in the input, and if both conditions are true, it raises a validation error. Validation is always called after the get value callback, since the result of the get value callback is what’s passed into the validators, which is why I can count on the input being empty at this point if all input has been successfully resolved.

And that’s it for rendering. The render method returns the HTML source from the outer div (i.e. outerHTML).

On Post Render

And that brings us to the post render method, whose primary responsibility is to attached event handlers to the now fully rendered fields. This includes applying Awesomplete to the entityEditorInput, because Awesomplete is just a bit of HTML, CSS, and behaviors (i.e. event handlers), and it too requires the field to be in the live DOM before it can do it’s magic. Here is the post render method:

First, we check to see if the field that was just rendered is one of our entity editor fields, and if not we don’t do anything. Otherwise, we start attaching event handlers, starting with creating an instance of Awesomplete. We initialize Awesomplete with the properties stored in the fieldMap property we created earlier. We also add the Awesomplete instance for each field back to the fieldMap, so event handlers can access it to remove entities from it’s source as they’re resolved, and add them back when resolved entities are removed.

We then add 3+ event handlers to 2+ DOM elements, to implement behaviors for our entityEditor. I’ll cover them in ascending order by size/complexity.

The handleEditorClick callback handles the click event on the entityEditor. On a click event anywhere inside the entityEditor, it will transfer focus to the entityEditorInput. Otherwise, in order to get focus on the input, the user would have to click directly on the input, which would be pretty annoying, since it has no border and is virtually invisible until focused (at which point at least it has a blinking cursor). Ok, they could also tab to it, but only a small subset of users use tab to navigate forms, or even know they can do that.

The next event receiver handles a custom event that is registered by Awesomplete called “awesomplete-selectcomplete”. This event fires after an item has been selected from the Awesomplete drop down, the value selected has overwritten the value of the input, and the drop down has closed. This method resolves the entity in the input, removing it from the Awesomplete list, adding an entity span to the entityEditor, and finally clearing the input. It also hides the entityEditorInput if the current choice field is single select, preventing the user from adding more entities.

The handleEntityRemove event receiver is applied to the click event of the small blue (x) anchor for each entity. This is why I said 3+ event handlers on 2+ DOM elements earlier, because this one could be added zero or more times to zero or more anchors, depending on how many resolved entities we have. It removes itself as an event handler from the entity span, in order to prevent resource leaks. It then removes the entity span, adds the entity value back to the source array for the Awesomplete, and re-initializes the Awesomplete instance with the updated source array. It also shows the entityEditorInput, if it was previously hidden (single select choice), so the user can choose another entity for this field.

The last event handler handles the key down event for the entityEditorInput. It does the following:

  • If the key pressed was the backspace key, and the entityEditorInput is empty, it removes the last resolved entity if there are any. This is just a convenience method, providing a quick way to clear out all entities from a multiple selection entity editor. If there are 10 resolved entities, the user can either click all 10 (x) anchors, or just focus the the entityEditorInput and hit the backspace key 10 times.
  • If the key is the return key, and if the Awesomplete dialog is open, “awesomplete-selectcomplete” event fires first, resolves the entity, and clears the entityEditorInput, so we do nothing if the input is empty. But if there is still text in the entityEditorInput, we need to try to resolve it. To do that we check the field schema in the fieldMap to see if the field allows fill-in choices. If yes, we resolve whatever they typed by adding it as a new resolved entity span and clear the input. If no, we raise a validation error.
  • Finally, on any other key, we clear any validation error, since the value has changed and we won’t know if it’s an error until the user attempts to resolve it again.

So that’s it, piece of cake right? I’ve now explained all of the code except for a couple of small, simple, helper functions that are well documented. The complete source code is available at the bottom of the post.

Sum Up

This is a pretty long blog post, but how could it not be, it’s mostly code. As for the narrative, I tried to explain what I’d want to cover if this were a SharePoint Saturday presentation. Of course, if this were really a presentation, I’d forget half of it, say “oh, I forgot this other thing” a lot, and jump back and forth in the code to explain those missed points. Come to think of it, I did that a lot while writing this too, you just weren’t with me for that part. 😉

As with the Star Ratings adapter, to get it deployed, just copy the files to your style library and use the utility page I described in Setting the JSLink Property of a Field Using JavaScript to set the JSLink property on each of the fields to which you want to apply the entity editor rendering template. That script only works on site columns. If you want to do this with say a list column, you’ll need to rework it a bit. Once applied, your fields should work like what I described above in the ‘What are we Building?’ section. Remember that we do have a dependency this time, on Awesomplete, and the utility page expects one JavaScript file per line, which will get loaded in order, so here is what that utility page looks like for one of my fields:

Set JSLink on Site Column

As a simple alternative, you could just copy the files up and set the JSLink property on a web part in the forms and views you want to apply this to. And of course, this will work regardless of whether you’re apply thing to site columns or list columns, but setting the JSLink on a site column is a better solution IMHO.

This will probably be my last post in this Client Side Rendering series. At this point, I’ve covered everything I wanted to cover. There’s always Search Display Templates, but if I decide to do that I’ll spin up a separate series dedicated to that, as this one has gotten quite long enough.

Reference

EntityEditorCSR.zip – the source code

Leave a Comment