Vanilla Fix™ Design Patterns: Essential Field Customisation Techniques

This article is a part of Vanilla Fix Design Patterns and How-To’s.

SharePoint. Front-end. jQuery. Functions. User experience. Data integrity. Newspeak. Not sure what these are? Stop reading, and do this instead.

Contents


The art of hiding

One of the most basic form customisation requirements is being able to hide certain fields. The need to hide fields, as opposed to physically removing them, arises from any combination of the following business cases:

  • Certain fields need to be included in the form, but they are likely to confuse and misdirect the person filling it out.
  • Some fields are auto-populated based on other fields or certain business logic. It makes sense to show them in view mode but not as editable fields in edit mode.
  • Values of certain fields cannot be determined by the user at the time of creating a new item. It is better to have such fields hidden initially than to offer wordy explanations.
  • In general, it is better to keep the form succinct, straightforward, and straight-to-the-point for the user, while also keeping all necessary data intact under the hood.

Important: Hiding a field, a set of ifelds, a set of choices, or any combination of visual elements through front-end techniques, does NOT provide a layer of additional security. The purpose of hiding elements on the front-end is to make the form as straightforward and streamlined to users as possible while respecting its business logic and maintainng data integrity. Protection of content, on the other hand, is configured through SharePoint permissions that are applied to sites, lists, and/or items.

A standard SharePoint list form renders each field in its own table row. The simplest way to hide a field is to hide the table row in which it appears. For example:

//-- Fix EF-1A [Hiding a field]
jQuery(vf.getField(_labelSuburb)).closest("tr").hide();
// vf.getField() is a method for better readability in jQuery syntax.
// _labelSuburb is a variable that has the field display name.

If, however, the form uses a custom layout, hiding a field simply becomes making a <span> invisible, as in the following example:

//-- Fix EF-1B [Hiding a field when a custom layout is used]
jQuery("span[id^='formFieldSuburb']").hide();
// The ID is from the custom layout HTML defined in vf-list-{name}.html.

Note. For clarity, the rest of this article assumes no custom form layout. The use of a custom layout is entirely optional and is covered in another article.

An important consideration here is WHEN does a particular field need to be hidden to the user, that is, viewing an item, creating an item, or editing an item? You would rarely hide a field in all three cases, unless the field itself is designed to play some purely administrative role under the hood, such as holding the result of a calculation or data transformation. For this reason, it is customary to place a routine to hide a set of fields inside either of these two functions:

  • definePresentationLogicForViewMode()
  • definePresentationLogicForEditMode():

As the names suggest, they kick in only in view mode (DispForm.aspx) and edit mode (NewForm.aspx or EditForm.aspx), respectively. If you need to be specific within edit mode about NewForm.aspx vs. EditForm.aspx, wrap your routine in a condition that examines vf.formMode, as in the following example:

//-- Fix EF-1C [Hiding a field as part of form initialisation]
function definePresentationLogicForEditMode() {
if (vf.formMode==1) {
jQuery(vf.getField(_labelApproval)).closest("tr").hide();
} // The field is hidden only when creating a new item.
}

Important: By defintion, only optional fields can be hidden. If you hide a mandatory field, that is, without any means to auto-populate it behind the scenes, there will be a conflict between what the user comprehends and what SharePoint expects. There are ways to make fields conditionally visible and conditionally mandatory, as described further down in this article.

As you would have guessed, show() does the reverse of hide().

Be careful when fields are named such that the display name of one field also makes a part of the display name of another field. For example, you may have a “Name” field and also a “Preferred Name” field in the same list, as follows:

// FIX EF-1D [Potentially clashing field display names]
var _labelName="Name";
var _labelPreferredName="Preferred Name";

Here, the following statement hides both “Name” and “Preferred Name” because both fields contain “Name”, which may not be the desired outcome.

// FIX EF-1E [Visibility logic affecting multiple fields]
jQuery(vf.getField(_labelName)).closest("tr").hide();

When you name fields in a list, avoid unwanted clashes under a contains test. For example, having “Legal Name” and “Preferred Name” ensures that the fields behave independently of each other. If you cannot avoid clashes, apply field visibility logic in the right sequence to get the intended result.

// FIX EF-1F [Getting the sequence of execution right]

// Name is shown, and Preferred Name is hidden.
jQuery(vf.getField(_labelName)).closest("tr").show();
jQuery(vf.getField(_labelPreferredName)).closest("tr").hide();

// Name is hidden, and Preferred Name is shown.
jQuery(vf.getField(_labelName)).closest("tr").hide();
jQuery(vf.getField(_labelPreferredName)).closest("tr").show();

// Both Name and Preferred Name are hidden.
jQuery(vf.getField(_labelPreferredName)).closest("tr").show();
jQuery(vf.getField(_labelName)).closest("tr").hide();

// Both Name and Preferred Name are shown.
jQuery(vf.getField(_labelPreferredName)).closest("tr").hide();
jQuery(vf.getField(_labelName)).closest("tr").show();

Table of contents


Conditionally visible fields

The method of hiding fields described above is “sticky.” That is because it becomes part of initialiseForm(), the form initialisation routine. Once hidden, a field stays hidden throughout that particular rendering of the form. There are, however, times where you need a hidden field or a set of hidden fields to show up again, when certain conditions are met during the process of filling out the form.

For instance, imagine a travel request form for employees at a company that has offices and customers across multiple geographical regions. The form may ask the user whether their planned trip is going to be reimbursed by a customer or not. If yes, a field for the name of the customer should appear on the form, ideally immediately below the question asking about reimbursement.

In this case, going straight to definePresentationLogicInEditMode() is not the answer. Instead, you want to head to registerFormEvents() and implement this in an event that is wired to the yes/no checkbox in the reimbursement question, as follows:

//-- Fix EF-2A [Registering an event bound to a checkbox field]
function registerFormEvents() {
if (vf.formMode>=1) { // Only for edit mode
jQuery("input[title^='"+_labelReimbursed+"']").change(function () {
if (jQuery("input[title^='"+_labelReimbursed+"']").is(":checked")) {
jQuery(vf.getField(_labelCustomer)).closest("tr").show();
} else {
jQuery(vf.getField(_labelCustomer)).closest("tr").hide();
}
});
}
}

As a side note: if the reimbursement field were a drop-down list instead of a yes/no checkbox, the event defintion would change as follows:

//-- Fix EF-2B [Registering an event bound to a drop-down list]
function registerFormEvents() {
jQuery("select[title^='"+_labelReimbursed+"']").change(function () {
if (jQuery("select[title^='"+_labelReimbursed+"']").val()=="X") {
// (Actions...)
}
});
}

In the above variation, val() will be used to examine the state of the field, instead of .is(“:checked”).

So far so good, but what if there were other fields that could also control the visibility of the same Customer field? In other words, the question about reimbursement may not be the only reason why a customer name is required. For example, the form may also ask “Are you travelling to a customer site (Yes/No)?” where, if yes, the name of the customer is naturally expected, independent of reimbursement.

If an action can be triggered by multiple controlling fields, you can certainly implement identical steps multiple times to register those events. But you would be repeatng the exact same routine about the same controlled field(s) across multiple controlling fields. This can lead to messy, hard-to-maintain code. A better alternative is to put all show/hide routines in a dedicated function and then get all relevant events to simply trigger that function. For example:

//-- Fix EF-3 [Defining event actions in a separate function]
function registerFormEvents() {
if (vf.formMode>=1) { // Only for edit mode

// Event wired to "To be reimbursed by a customer?"
jQuery("input[title^='"+_labelReimbursed+"']").change(function () {
conditionallyCustomiseFields();
});

// Event wired to "Travelling to a customer site?"
jQuery("input[title^='"+_labelToCustomerSite+"']").change(function () {
conditionallyCustomiseFields();
});
}
}

function conditionallyCustomiseFields() {

// Set the visibility of the Customer field by examining
// the fields about reimbursement and going to a customer site.
if ((jQuery("input[title^='"+_labelReimbursed+"']").is(":checked"))
||(jQuery("input[title^='"+_labelToCustomerSite+"']").is(":checked"))
) { // If either of these fields is a yes, then show the Customer field.
jQuery(vf.getField(_labelCustomer)).closest("tr").show();
} else {
jQuery(vf.getField(_labelCustomer)).closest("tr").hide();
}
}

Now all show/hide logic is contained in a single generically named function, conditionallyCustomiseFields(), which can examine multiple controlling fields in a sequence or in parallel as required. You can keep adding to this function more customisation routines that correspond to events that are bound to other fields in the same SharePoint list or library. Each individual field-bound event, defined within registerFormEvents(), then simply triggers conditionallyCustomiseFields(). This is a design for better readability and re-usability, and that is why the pattern is baked into vf-list-boilerplate.html.

If you think about it, it makes sense to execute conditionallyCustomiseFields() not only when an event is triggered, but also during form initialisation for edit mode. That is because the user needs to see only the fields that are applicable to either the form’s default state (as in NewForm.aspx) or the state of the currently selected item (as in EditForm.aspx) – even before starting to interact with the form. For this reason, vf-list-{name}.html runs conditionallyCustomiseFields() inside definePresentationLogicForEditMode() by default. This works for both vf.formMode of 1 (NewForm.aspx) and 2 (EditForm.aspx).

This is the gist of implementing conditionally visible fields, but there are a couple of loose ends to take care of:

  • When creating a new item, fields that are conditionally hidden and shown should start off hidden (although there may be exceptions). As such, default states of fields that control the behaviour of other fields should be set with this in mind. In the above company travel request form example, both of the checkbox fields asking about reimbursement and going to a customer site, should be unchecked (set to No) by default in the respective column definition.
  • When a field becomes visible upon a condition, it may also need to become mandatory. But a field that can be hidden must be optional by definition. A couple of front-end enhancement patterns address this grey area. See the following sections about conditionally mandatory fields and conditionally visible mandatory fields.

Table of contents


Conditionally mandatory fields

The preceding section looked at conditionally visible fields. A topic just as frequently considered and discussed for thoughtfully customised forms is conditionally mandatory fields.

Suppose, in the company travel request form example used above, there is a text field where the user can specify preferred accommodation for their upcoming trip. Normally, this field is optional. But there is a business rule that says: when an employee travels to a customer site, they must provide details of their preferred accommodation. No matter what fictitious business rationale gave rise to this rule, it needs to be implemented in the travel request form.

Conditionally switching between optional and mandatory

The fix comes in two parts: behavioural and cosmetic. The behavioural part captures and responds to omissions through form data validation in PreSaveItem(). The key to utilising PreSaveItem() is to:

  • specify the precise condition upon which validation kicks in, so that there are no false-positives (or negatives); and
  • explain to the user clearly what did not pass validation and what they can do about it, through appropriate alerts and visual guidance.

In this case, validation would look like the following:

//-- Fix EF-4A [Using PreSaveItem() for a conditionally mandatory field]
function PreSaveItem() {

// (Following other validation routines...)

// If the user is going to a customer site, ensure that they have
// supplied details of preferred accommodation.
if (jQuery("input[title^='"+_labelToCustomerSite+"']").is(":checked")) {
if (jQuery("input[title^='"+_labelPrefAccomm+"']").val()=="") {
alert(
"If you are going to a customer site, please specify"
+" your preferred accommodation."
);
jQuery("input[title^='"+_labelPrefAccomm+"']").focus();
return false;
}
}
}

This completes the behavioural part of the fix for a conditionally mandatory field.

Table of contents


All about the visual cue

Functionally, the code above (Fix EF-4A) alone would qualify as a minimum viable product (MVP) demonstrating a conditionally mandatory field. From a usability standpoint, however, there is a hole that needs to be plugged.

Without upfront visual cues, a user who is travelling to a customer site will almost always be forced to go through a failed save attempt before learning to put something in the Preferred Accommodation field. This is all because the Preferred Accommodation field appears to be optional – which it is, by defintion – all of the time. But because of said business rule, there is now an edge case where the way this field appears contradicts the way it behaves.

To address such a disastrous violation of Usability 101, the fix also requires a cosmetic aspect. Programmatically show an asterisk, or any visual cue of your choosing, next to the Preferred Accommodation field label when the checkbox for the are-you-going-to-a-customer-site field is checked. Because Preferred Accommondation is an optional field by definition, however, you must supply your own asterisk. To do this, inject one during form initialisation as follows:

//-- Fix EF-4B [Adding an asterisk to the label of an optional field]
function definePresentationLogicForEditMode() {

// Add an asterisk to the label of Preferred Accommondation.
jQuery(vf.getField(_labelPrefAccomm)).after(vf.reqSpan);
}

Here, vf.reqSpan is just a predefined snippet of HTML brought over from vf-sp.js. You can also use your own <span> to render an indication that a field is mandatory.

Next, register an event for the controlling field, one that will simply trigger conditionallyCustomiseFields(). You can adopt the pattern used in the previous section about conditionally visible fields.

//-- Fix EF-4C [Ensuring that a field responds to a change]
function registerFormEvents() {
If (vf.formMode>=1) {

// (Following other events...)

// Event: "Are you going to a customer site?" question
jQuery("input[title='"+_labelToCustomerSite+"']").change(function () {
conditionallyCustomiseFields();
});
}
}

Now, the actual conditional logic takes place in conditionallyCustomiseFields() using the following pattern:

//-- Fix EF-4D [Conditionally marking a field as mandatory]
function conditionallyCustomiseFields() {

// (Following other customisation routines...)

// Switch the asterisk on and off based on the state of the
// are-you-going-to-a-customer-site field.
if (jQuery("input[title='"+_labelToCustomerSite+"']").is(":checked")) {
jQuery(vf.getField(_labelPrefAccomm)).next("span").show();
} else {
jQuery(vf.getField(_labelPrefAccomm)).next("span").hide();
}
}

With this usability enhancement added, an optional field finally looks and behaves like a mandatory field when a certain condition is satisfied. It may seem like a lot of JavaScript going into a minor enhancement after all, but it absolutely contributes to a design for better user adoption.

To sum up the pattern for conditionally mandatory fields:

  1. Perform validation in PreSaveItem() to ensure that the controlled (target) field has been attended to.
  2. Ensure that the controlling field responds to changes by registering an event in registerFormEvents().
  3. Add an asterisk, or any mark of your choosing, to the label of the controlled (target) field during form initialisation, that is, in definePresentationLogicForEditMode().
  4. In the action triggered by the event defined in Step 1, switch on and off the asterisk based on the current state of the controlling field. Configure this action to fire both during form initialisation and on demand by placing it in conditionallyCustomiseFields().

Table of contents


Conditionally visible mandatory fields

Having looked at conditionally visible fields and conditionally mandatory fields, there is a follow-up pattern that is essentially a combination of the two: conditionally visible mandatory fields. Here, the visibility of a mandatory field is determined by a condition. Once again, the challenge is that the field is actually optional by definition. If it remains hidden, it remains optional. If it becomes visible, however, it must turn into a mandatory field.

To illustrate an example, bring back the earlier company travel request form scenario from the conditionally visible fields section. The question of reimbursement determines the visiblity of the Customer field. (For simplicity, let’s not worry about the are-you-going-to-a-customer-site field in this section.)

A conditionally visible mandatory field

To reiterate: the issue is that Customer is an optional field by definition. Even if you catch an omission in PreSaveItem(), it is considered incomplete because the form does not inform the user upfront that Customer is required if the trip is going to be reimbursed.

As it turns out, implementing conditionally visible mandatory fields is practically identical to the pattern for conditionally mandatory fields, with one key difference: instead of controlling the visibility of an asterisk next to the label of a field, control the visibility of the entire field whose label has a programmatically created asterisk built in.

The pattern follows the same example code (Fixes EF-4A through 4C) for PreSaveItem(), registerFormEvents(), and definePresentationLogicForEditMode(). The controlling field and the controlled field in this particular example, of course, change to Reimbursement and Customer, respectively. The difference is what happens in conditionallyCustomiseFields(), as illustrated below:

//-- Fix EF-5A [Conditionally showing and hiding a pseudo-mandatory field]
function conditionallyCustomiseFields() {

// (Following other customisation routines...)

// Show or hide the Customer field based on the current state of
// the reimbursement question.
if (jQuery("input[title='"+_labelReimbursement+"']").is(":checked")) {
jQuery(vf.getField(_labelCustomer)).closest("tr").show();
} else {
jQuery(vf.getField(_labelCustomer)).closest("tr").hide();
}
}

The entire target field, as opposed to just the asterisk, is switched on and off.

Table of contents


Due diligence: Clean up after your field

There is, however, a twist to the above fix: What if the user initially provided a customer name as required, saved the data, but later came back and removed the requirement to provide a customer name by unchecking the reimbursement checkbox, for example?

The Customer field is now invisible, but its value still remains under the hood. You may think that is okay since the field is hidden anyway, but the data may still be exposed in view mode (DispForm.aspx) and/or in a SharePoint list view that includes the Customer column. More importantly, if left unaddressed, this introduces ambiguity in the business rule. Leaving room for compromised data integrity is not a clean solution.

The right thing to do is to clean up after your fields. If a particular optional or pseudo-mandatory field is deemed not applicable to the state of the current item, then eliminate room for confusion by ensuring that the field carries no data in it. This can be taken care of in PreSaveItem() as follows:

//-- Fix EF-5B [Clearing out a field for the sake of data integrity]
function PreSaveItem() {

// When the Customer field is visible, it must not be left blank.
if (jQuery(vf.getField(_labelCustomer)).is(":visible") {
if (jQuery("input[title^='"+_labelCustomer+"']").val()=="") {
alert("Oops! Please provide the name of the customer.");
jQuery("input[title^='"+_labelCustomer+"']").focus();
return false;
}
} else { // If not, make it blank regardless of what it had before.
jQuery("input[title^='"+_labelCustomer+"']").val("");
}

// Also perform validation against other fields...
return true;
}

A conditionally visible mandatory field has now been implemented.

Table of contents


To Title or not to Title

Another “essential” SharePoint form customisation pattern has to do with the treatment of the Title field. In SharePoint, Title is baked into every list and library. In many cases, this is perfectly fine and useful; Title is indeed the most appropriate field to uninquely identify a content item by – except when it isn’t.

Going back to the hypothetical company travel request form again, the first required (mandatory) field a user gets to see and ponder when they create a new request or modify an existing one, is Title. It is, however, unclear what is expected in this field in order for it to serve as a useful identifier, especially when it needs to be supplied by the user. What actually matters to the user are details such as travel itinerary, preferred mode of transportation, preferred accommodation, customer or project information if applicable, and so on, but a “title?”

Such is a frequently observed predicament in any SharePoint-powered information system. Having recognised that Title is not always useful or applicable, developers tend to jump on to the usual modus operandi of surgically removing, renaming, or otherwise altering this built-in field on the back-end. While doable, widely documented, and fairly commonplace, these methods amount to hacking the native structure of containers in SharePoint.

There is a far less destructive alternative, which is an application of some of the front-end field customisation techniques introduced in this article. The pattern for non-destructively re-purposing the Title field is as follows:

  1. Keep the Title field intact in the list or library settings. (This is a good start.)
  2. Unconditionally hide the Title field in both view and edit modes using the pattern for hiding fields described above.
  3. Rank the rest of the required (mandatory) fields in the list or library in terms of usefulness as metadata. Pick the top one, two, or three key fields as appropriate.
  4. In PreSaveItem(), after all validation routines have been performed, read in the values of the key fields, sanitise them, and assemble them into a single string. Put the assembled string into the Title field. In the case of the travel request form example, it may be a combination of the requester’s name (built-in column), destination, and the planned date of departure, concatenated.

Now the Title field holds meaningful metadata in itself, and all of this is done automatically without user intervention. Title as a field stays hidden in the list forms, but can still be shown in list views for easy browsing of items; after all, Title is the column users click into in order to open the details of an item. What’s more, values stored in the Title field are always indexed and searchable.

This pattern also gives rise to methods of transparent calculation and data transformation beyond and independent of the Title field.

Table of contents


Back to Vanilla Fix Design Patterns and How-To’s

Questions and thoughts about Vanilla Fix can be sent to vanillafix@kimmaker.com