2

Looking at implementing column or list validation for a date field to stop them from entering past dates

The validation should do the following

  1. Validate only there is a value in the column
  2. Validate only if this particular field has changed. E.g. a user entered today's date and saved the item. They come back and change some other fields but not the date field in question they should be allowed to save

However if they change the date field it should validate that the date is not a past date

1

This is possible using JSLink/CSR client-side validation. That is, if you are on a version of SharePoint that still has JSLink/CSR available.

From my understanding, SP Online modern experience does not use the JSLink/CSR system from older versions. I am unsure if SP Online classic experience still has that (can someone who knows confirm in a comment?). SP 2013 / 2016 on-prem definitely has JSLink/CSR available to tap into (and I believe 2019 as well? Again, not sure on that one...)

Here's some example code (tested on 2013):

var MyNamespace = MyNamespace || {};

MyNamespace.PreviousFieldValue = null;

MyNamespace.MyFieldValidator = function myFieldValidator () {

    MyNamespace.MyFieldValidator.prototype.Validate = function (fieldValue) {
        var isError = false;
        var errorMessage = '';

        // only validate if the field has a value
        if (fieldValue) {

            // assume that we do need to check if it is a past date
            var shouldCheckIfPastDate = true;
            var enteredDate = new Date(fieldValue);

            // if there was a previous value, check to see if it has changed
            if (MyNamespace.PreviousFieldValue !== null) {

                var previousDate = new Date(MyNamespace.PreviousFieldValue);

                // if the date has not changed, don't check if it's a past date
                // Date object can't do equality comparison natively, so we have to use getTime()
                if (enteredDate.getTime() === previousDate.getTime()) {
                    shouldCheckIfPastDate = false;
                }
            }

            if (shouldCheckIfPastDate) {

                // get todays date and zero out the time so we are only comparing the date
                var today = new Date();
                today.setHours(0, 0, 0, 0);

                // Date object can handle greater than/less than comparison natively
                if (enteredDate < today) {
                    isError = true;
                    errorMessage = 'You cannot enter a past date.';
                }
            }
        }

        return new SPClientForms.ClientValidation.ValidationResult(isError, errorMessage);
    }
}

MyNamespace.MyFieldFieldOverride = {
    edit: function edit (ctx) {

        // if it's not blank, store the current value of the field to check against later
        if (ctx.CurrentFieldValue) {
            MyNamespace.PreviousFieldValue = ctx.CurrentFieldValue;
        }

        // get the form context and create a new validator set
        var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
        var fieldValidators = new SPClientForms.ClientValidation.ValidatorSet();

        // if the field has been marked as required, register the default required field validator
        if (formCtx.fieldSchema.Required) {
            fieldValidators.RegisterValidator(new SPClientForms.ClientValidation.RequiredValidator());
        }

        // register our custom validator
        fieldValidators.RegisterValidator(new MyNamespace.MyFieldValidator());

        // add the error callback and register the validator set with the form context
        formCtx.registerValidationErrorCallback(formCtx.fieldName, MyNamespace.MyFieldFieldOverride.onError);
        formCtx.registerClientValidator(formCtx.fieldName, fieldValidators);

        // render the default form control for a date column
        var returnHtml = SPFieldDateTime_Edit(ctx);

        // add a spot for our custom error message
        returnHtml += "<span id='MyFieldCustomError' class='ms-formvalidation ms-csrformvalidation'></span>";
        return returnHtml;
    },
    onError: function onError (error) {
        document.getElementById('MyFieldCustomError').innerHTML = "<span role='alert'>" + error.errorMessage + "</span>";
    },
    render: function render () {
        SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
            Templates: {
                Fields: {
                    YourDateField: {
                        'NewForm': MyNamespace.MyFieldFieldOverride.edit,
                        'EditForm': MyNamespace.MyFieldFieldOverride.edit
                    }
                }
            }
        });
    }
}

// handle MDS-enabled or MDS-disabled situations
RegisterModuleInit(SPClientRenderer.ReplaceUrlTokens('~site/path/to/MyFieldValidator.js'), MyNamespace.MyFieldFieldOverride.render);
MyNamespace.MyFieldFieldOverride.render();
| improve this answer | |
  • Thank you Dylan. This worked perfectly. – user3799810 Jun 27 '19 at 10:01
  • Hi Dylan, actually need to do same validation for multiple fields. to do so i added YourDateField1: { 'NewForm': MyNamespace.MyFieldFieldOverride.edit, 'EditForm': MyNamespace.MyFieldFieldOverride.edit }, YourDateField2: { 'NewForm': ProviderPortal.FieldFieldOverride.edit, 'EditForm': ProviderPortal.FieldFieldOverride.edit } however it looks like the JS is getting confused as to which field it is dealing with. – user3799810 Jul 5 '19 at 7:38
  • Is there anyway to figure out which field are we dealing with in the Validate Functions – user3799810 Jul 5 '19 at 7:39
  • Unfortunately no, the validate function only gets passed the field value, not context information, so you won't know which field the value is coming from. There might be a way to separate out some of the validation logic so that it can be shared, but you would still need to store the PreviousFieldValue separately for each field, have individual MyField1_Validator, MyField2_Validator functions, as well as have separate edit_field1, edit_field2, etc, functions to register the individual validator functions. – Dylan Cristy Jul 5 '19 at 16:09
  • It might seem like a lot of overkill, but I would almost say the easiest thing would be to not just add additional fields to the RegisterTemaplateOverrides section in one script, but recreate the entire script for each of the fields you need to have validated. – Dylan Cristy Jul 5 '19 at 16:11
4

As far as I know, it's impossible to validate only if particular field changes. It's by default that column validation would validate the data in this column when items are saved to this list.

I could achieve your first requirements using this formula:

=IF(ISBLANK([Date]),TRUE,[Date]>=TODAY())
| improve this answer | |
  • Thanks Michael. I thought the same too. Just wanted to confirm – user3799810 Jun 26 '19 at 9:00
  • Please accept the reply as answer if it helps. Thanks for your understanding – Michael Han_MSFT Jun 26 '19 at 9:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.