outside.jpg

SharePoint List Forms with jQuery

SharePoint List Forms with jQuery

SharePoint provides some great “out of box” features to get a departmental application up and running quickly. However, there are often requirements that fall outside the “out of box” capabilities. jQuery, along with many jQuery plugins that have been written, is a great tool that can be used to help fill in these gaps.

Here are a few ways I have used jQuery to supplement what SharePoint gives me for free:

  • Enforce field-level permissions in a SharePoint form
  • Create parent/child relationships between fields in a SharePoint form
  • Enforce custom validation in a SharePoint form such as conditionally required fields
  • Add “autocomplete” to a text box to filter down valid entries as the user types in the field

First of all, why would I want to enforce field-level permissions? Let’s suppose I create a custom list in SharePoint that I want to use for both submitting and approval of expenses. I would need the person submitting the expense to be able to add and update entries in this list, and I would also need the “expense approver” to also update the same list items. However, I wouldn’t want the “submitter” of the expense to be able to fill out fields that are specific to the “expense approver.” Within SharePoint, I can create a custom SharePoint Group in which I place users who are “Approvers.” I can then add jQuery to my SharePoint form that determines if the current user is a member of the custom Approvers group created above. If not, I can disable the field. If so, I can leave the field enabled. Note this is not true security but is a nice way to help guide users as they input data as part of a departmental or inter-departmental process. There are ways for a malicious user with the right permissions to work around this by creating a new add or edit form for this list that doesn’t contain the jQuery. However, an event receiver (server-side code) can easily be added to validate who is updating these fields on the server and reject the change if the user doesn’t have the appropriate privileges.

A second scenario that jQuery can help solve is the desire to have a parent/child relationship within a SharePoint form. A simple example of this would be the relationship between States and Counties. If I select the state of Georgia, I should only see counties in the Counties field that relate to Georgia. Otherwise, a user might select state/county combinations that are invalid which would require extra time and money to determine which is correct.

A third and final scenario to discuss in this blog is the desire to have conditionally required fields. By default, SharePoint allows configuration of a required field, but this field is required all the time. What if a field should only be required conditionally? What if the Expense Approved Date is required for the expense to be approved? When the submitter of the expense enters the expense, there is no way they can know the Approved Date nor would it even be appropriate for them to enter it since they are not the approver. The ability to implement a conditionally required field solves this problem.

Implementation Details for Programmers or Power Users

By default, SharePoint creates 3 forms for a new custom list:

  • DispForm.aspx
  • EditForm.aspx
  • NewForm.aspx

Each of these forms can be edited with SharePoint Designer to add in the appropriate code to “wire up” jQuery. I typically use the PlaceHolderAdditionalPageHead ContentPlaceHolder.

The example below demonstrates:

  • Referencing the jQuery file that is stored locally in the SiteAssets folder. The minified version of the jQuery file has been downloaded and added to a scripts sub-folder within the SiteAssets library. In this scenario, SPServices and blockUI javascript libraries are also being used so they have been downloaded locally and are also referenced.
  • An application javascript file called myApplication.js where application code will go.
<asp:Content ContentPlaceHolderId="PlaceHolderAdditionalPageHead" runat="server">
	<SharePoint:DelegateControl runat="server" ControlId="FormCustomRedirectControl" AllowMultipleControls="true"/>
	<SharePoint:UIVersionedContent UIVersion="4" runat="server"><ContentTemplate>
		<SharePoint:CssRegistration Name="forms.css" runat="server"/>
	</ContentTemplate></SharePoint:UIVersionedContent>
	<!—add my stuff here →
        <script src="/SiteAssets/scripts/jquery-2.1.3.min.js" type="text/javascript"></script>
	<script src=”/Siteassets/scripts/jquery.SPServices-2014.01.js" type="text/javascript"></script>
	<script src="/Siteassets/scripts/blockUI.js" type="text/javascript"></script>
	<script src="/SiteAssets/scripts/myApplication.js" type="text/javascript"></script>
</asp:Content>

Flow of Code

After the page loads,the typical flow of the code is, retrieval of the current user from SharePoint so I can determine which SharePoint groups the current user is a member and then disabling or hiding any fields that may not be applicable to this user. Because this code can take a second or two to run, I use a jQuery plug-in called blockUI to block the screen from user input during this time. I also leverage SPServices (http://spservices.codeplex.com/) to make it easier to consume the SharePoint web services.   You will see both of these referenced in the code excerpts below.

Start script after page is loaded and after the sp.js file is loaded to ensure all form elements exist and the appropriate SharePoint libraries are loaded

$( document ).ready(function() {

ExecuteOrDelayUntilScriptLoaded(InitContext, "sp.js");

});

Populate local user object from the server

function InitContext() {

//block user input while the jQuery code runs

$.blockUI();

try {

// current user object

this.cUser = new Object();

this.cUser.groups = new Array();

this.cUser.id = "";

this.cUser.loginName = "";

var ctx = new SP.ClientContext.get_current();

currentWeb = ctx.get_web();

usrLoad = this.currentWeb.get_currentUser();

usrLoad.retrieve();

ctx.load(this.currentWeb);

ctx.executeQueryAsync(Function.createDelegate(this, this.initContextSucceeded),

Function.createDelegate(this, this.initContextFailed));

}

finally {

}

}

Success Delegate

function initContextSucceeded() {

try {

cUser.loginName = this.usrLoad.get_loginName();

cUser.id = this.usrLoad.get_id();

loadUserGroups(cUser.loginName);

UpdateForm();

$.unblockUI();

}

finally {

}

}
<h3>Code to load SharePoint Groups</h3>
// Retrieve the group membership for the current user

function loadUserGroups(loginName) {

$().SPServices({

operation: "GetGroupCollectionFromUser",

userLoginName: loginName,

async: false,

completefunc: function (xData, Status) {

$(xData.responseXML).find("Group").each(function () {

cUser.groups.push($(this).attr('Name'));

});

}

})

}

Failure Delegate

// Failure callback for initContext async query

function initContextFailed(sender, args) {

$.unblockUI();

alert('Retrieval of initial context failed.\nMessage: ' + args.get_message() + '\nStack Trace: ' + args.get_stackTrace())

}

Enforce Field-Level Permission

The UpdateForm function contains an example of a “field-level” permission based on a user’s role which is indicated by their membership or lack of in a custom SharePoint group called “InventorySpecialist”.

Update Form – Add code here to disable any fields based on user’s membership (or lack of membership) in a particular SharePoint group

function UpdateForm() {

//Disable Status field if user is not in the Inventory Specialist SharePoint group

if ($.inArray('InventorySpecialist',cUser.groups) == "-1" {

$("select[title=Status']").attr("disabled",true);

}

//bind an event handler to the change event of the State drop-down

$("select[title='State']").change(StateChanged);

//trigger the change event

$("select[title='State']").change();

}

Enforce Parent/Child Relationship

The parent/child relationship enforcement is demonstrated using the State/County example in the UpdateForm function. A “change” event handler is bound to the State drop-down such that when the state changes, a new set of County values are populated. The example below is greatly simplified for the purpose of this blog, but the code in the change event handler would most likely make a caml query call to the look-up list associated to the Counties column to get all the counties associated to the State so that the value the user selects will be a valid value when saved back into SharePoint.

Update Form – Add code here to disable any fields based on user’s membership (or lack of membership) in a particular SharePoint group

function UpdateForm() {

//Disable Status field if user is not in the Inventory Specialist SharePoint group

if ($.inArray('InventorySpecialist',cUser.groups) == "-1" {

$("select[title=Status']").attr("disabled",true);

}

//bind an event handler to the change event of the State drop-down

$("select[title='State']").change(StateChanged);

//trigger the change event

$("select[title='State']").change();

}

function StateChanged() {

//get value of state drop-down

var state =$("select[title='State']").val();

//get a reference to the County drop-down to load new counties below

var countySelection = $("select[title='County']");

switch (state) {

case '(None)':

break;

 

case 'Alabama': 

counties = ['Baldwin','Barbour','Bibb'];

break;

case 'Georgia':

counties = ['Cobb','Forsyth','Fulton'];

break;

default:

break;

}

//remove options from County drop-down

$("select[title='County']")

.find('option')

.remove()

;

foreach (county in counties) {

countySelection.append('<option value="' + county + '">' + county + '</option>');

}

}

Add custom validation

The custom validation is demonstrated in the PreSaveItem function. SharePoint automatically calls this function before posting to the server giving the developer a chance to insert any custom validation. In this example, I am ensuring that the user selected a “Location” value in the location lookup. I could easily make this check conditional on whether the current user is a member of the InventorySpecialist SharePoint group by wrapping the condition in another if similar to the one I used in the UpdateForm function where I conditionally disabled the Status drop-down. This would be an example of a “conditionally required” field.

Add validation code which runs on click of Save button

function PreSaveItem()

{

//add form validation logic here

$.blockUI({

message: '<H2 class="ms-stylelabel">Validating Form...</H2>',

centerX: true,

centerY: true,

fadeIn: 400,

fadeOut: 600,

timeout: 0,

overlayCSS: {

backgroundColor: '#000000',

opacity: 0.10

}

});

var returnCode = true;

var firstError = true;

errorMessage = "";

//validate input; set focus to first field in error

if ($("select[title='Location Lookup'] option:selected").text() == ""){

returnCode = false;

//set focus to the field if it is the first error

if (firstError) {

$("select[title='Location Lookup']").focus();

}

firstError = false;

buildErrorMessage("- A valid Location must be selected. ");

}

if (!returnCode) {

alert(errorMessage);

//add form validation logic here

$.unblockUI();

}

return returnCode;

}

Function to build a formatted error message with each error on a separate line

function buildErrorMessage(msg) {

if (errorMessage == "") {

errorMessage = msg;

}

else {

errorMessage += "\n" + msg;

}

}

Here’s a more full listing of the code as you would see it if you opened up myApplication.js in SharePoint Designer.

//Start script after page is loaded and after the sp.js file is loaded to ensure all form elements exist and the //appropriate SharePoint libraries are loaded

$( document ).ready(function() {

ExecuteOrDelayUntilScriptLoaded(InitContext, "sp.js");

});

//Populate local user object from the server

function InitContext() {

$.blockUI();

try {

// current user object

this.cUser = new Object();

this.cUser.groups = new Array();

this.cUser.id = "";

this.cUser.loginName = "";

var ctx = new SP.ClientContext.get_current();

currentWeb = ctx.get_web();

usrLoad = this.currentWeb.get_currentUser();

usrLoad.retrieve();

ctx.load(this.currentWeb);

ctx.executeQueryAsync(Function.createDelegate(this, this.initContextSucceeded),

Function.createDelegate(this, this.initContextFailed));

}

finally {

}

}

//Success Delegate

function initContextSucceeded() {

try {

cUser.loginName = this.usrLoad.get_loginName();

cUser.id = this.usrLoad.get_id();

loadUserGroups(cUser.loginName);

UpdateForm();

$.unblockUI();

}

finally {

Sys.Debug.trace('initContextSucceeded End');

}

}

//Failure Delegate

// Failure callback for initContext async query

function initContextFailed(sender, args) {

$.unblockUI();

alert('Retrieval of initial context failed.\nMessage: ' + args.get_message() + '\nStack Trace: ' + args.get_stackTrace())

}

//Code to load SharePoint Groups</strong>

// Retrieve the group membership for the current user

function loadUserGroups(loginName) {

$().SPServices({

operation: "GetGroupCollectionFromUser",

userLoginName: loginName,

async: false,

completefunc: function (xData, Status) {

$(xData.responseXML).find("Group").each(function () {

cUser.groups.push($(this).attr('Name'));

});

}

})

}

//Update Form – Add code here to disable any fields based on user’s membership (or lack of membership) in a //particular SharePoint group

function UpdateForm() {

//Disable Status field if user is not in the Inventory Specialist SharePoint group

if ($.inArray('InventorySpecialist',cUser.groups) == "-1" {

$("select[title=Status']").attr("disabled",true);

}

//bind an event handler to the change event of the State drop-down

$("select[title='State']").change(StateChanged);

//trigger the change event

$("select[title='State']").change();

}

//Function to change the County values available in the County drop-down based on the selected State

function StateChanged() {

//get value of state drop-down

var state =$("select[title='State']").val();

//get a reference to the County drop-down to load new counties below

var countySelection = $("select[title=’County’]");

switch (state) {

case '(None)':

break;

 

case 'Alabama':

counties = [‘Baldwin’,’Barbour’,’Bibb’];;

break;

case 'Georgia':

counties = [‘Cobb’,’Forsyth’,’Fulton’];

break;

default:

break;

}

//remove options from County drop-down

$("select[title=‘County’]")

.find('option')

.remove()

;

foreach (county in counties) {

countySelection.append('<option value="' + county + '">' + county + '</option>');

}

}

//Add validation code which runs on click of Save button

function PreSaveItem()

{

//add form validation logic here

$.blockUI({

message: '<H2 class="ms-stylelabel">Validating Form...</H2>',

centerX: true,

centerY: true,

fadeIn: 400,

fadeOut: 600,

timeout: 0,

overlayCSS: {

backgroundColor: '#000000',

opacity: 0.10

}

});

var returnCode = true;

var firstError = true;

errorMessage = "";

//validate input; set focus to first field in error

if ($("select[title='Location Lookup'] option:selected").text() == ""){

returnCode = false;

if (firstError) {

$("select[title='Location Lookup']").focus();

}

firstError = false;

buildErrorMessage("- A valid Location must be selected. ");

}

if (!returnCode) {

alert(errorMessage);

//add form validation logic here

$.unblockUI();

}

return returnCode;

}

//Function to build a formatted error message with each error on a separate line

function buildErrorMessage(msg) {

if (errorMessage == "") {

errorMessage = msg;

}

else {

errorMessage += "\n" + msg;

}

}

If you have questions please leave a comment below…

Tim CoalsonSharePoint List Forms with jQuery

9 comments

Join the conversation
  • Eric Bowden - February 6, 2015 reply

    These are great ways to amplify SharePoint “out of the box” features, thanks for the great post.

    Can these techniques be applied to SharePoint in Microsoft 365? If so, how should enhancements like these be deployed?

    Kirk Liemohn - February 6, 2015

    Yes! SharePoint Designer is one way. PnP CSOM provisioning is another.

  • Grant Lewis - February 6, 2015 reply

    Are there any advantages or disadvantages to adding the script tag to the page directly in SharePoint Designer versus using the JSLink property of the List View Web Part to load the script?

    Eric Bowden - February 6, 2015

    Now that you mention it, I like the idea of using JSLink because it should be easier to activate and deactivate these custom jQuery features.

  • aterese - September 9, 2015 reply

    I am going mad, or does anyone else find in O365 you cannot edit the content placeholder code is protected and therefore you cannot insert the jquery links here. I am currently going for inserting script web part on the form pages.

  • Дэги Караев - February 15, 2016 reply

    What do you think about PDF form integration to SharePoint? I like PDF forms, ’cause it’s file and could be converted virtually from any other document.

  • venkatesh - July 8, 2016 reply

    Hi,

    I want to find the specific sharepoint person or group column using jquery to validate the field

    Please help

    Thanks,
    Venky

  • Austin Scott - November 18, 2016 reply

    I’m trying real hard to make this work to validate a due date. If the due date required field is within 5 days, I want to error.

    I’m really close, but I’m getting an error when PreSave function runs.

    Uncaught TypeError: $.blockUI is not a function

    and highlights this section:

    $.blockUI({

    message: ‘<H2 class="ms-stylelabel">Validating Form…</H2>’,

    centerX: true,

    centerY: true,

    fadeIn: 400,

    fadeOut: 600,

    timeout: 0,

    overlayCSS: {

    backgroundColor: ‘#000000’,

    opacity: 0.10

    }

    tcoalson - November 18, 2016

    Blockui is a separate Javascript library. If you aren’t using it, remove any references to it from the Javascript so you don’t get the error. Blockui is a library that blocks user input.

Join the conversation

This site uses Akismet to reduce spam. Learn how your comment data is processed.