Highlight changes from the last save in DispForm or EditForm

This solution is designed for use in Custom JS in a DFFS enabled form. It will highlight the changes made in he last save so the person viewing the DispForm or EditForm can quickly identify what fields have changed and what was changed.

Please note that it is only tested in SharePoint online, but it might also work in an on-premises 2016 or 2019 SharePoint form.

This is how it looks in the form:

And when you click the icon to the right of the highlighted row you can view the actual changes:

How to install

Just add this code to your Custom JS and you are good to go:

spjs.highlightChanges = {
    "version": "1.0.0.0",
    "settings": {
        "highlightStyle": {
            "backgroundColor": "#ffaa44"
        },
        "iconSrc": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAABBUlEQVQ4T9WTPQrCQBCFcwCrgOexEmwDHsA6EPLXCwHBGwg5iZVtwMobBAQPIKQShPje8nCJWRPt9MGwmTff7M+g3n8oDMNJmqbrLMsqxF1R0WNN2LAAz5IkqdF4wbrDumLom15NRrhbAALALdbSdbJuWooJZHeFgg+gISjLCPmBodSIjFhflhWuvEHx/HoTeHuGUiMyZNkjywqFE07YKh0VWfYotYrj+Jbn+VLpU4BbhtKnyKLnqtSK5rcbwW+UWsF0Pu3dRppp/2kDw+5tNDjsKIqmKDYo7mQZuTYiQ5Y9srrSuz/6Qbrm2RGgOU4c+4sshA9LMygQR4R5mr4L101/UZ73ADXvzFKUWbsAAAAAAElFTkSuQmCC",
        "iconTooltip": "Show changes made in last edit",
        "dlgTitle": "Changes in the last version for",
        "currentVersionLabel": "Current version",
        "previousVersionLabel": "Previous version",
        "modifiedLabel": "Modified ",
        "modifiedByLabel": " by "
    },
    "init": function () {
        jQuery.ajax({
            "url": _spPageContextInfo.webAbsoluteUrl + "/_api/Web/Lists/getbyid('" + _spPageContextInfo.pageListId + "')/items('" + spjs.dffs.data.thisItemID + "')/?$select=Versions/VersionId&$expand=Versions",
            "method": "GET",
            "headers": {
                "accept": "application/json; odata=verbose",
                "content-type": "application/json;odata=verbose",
                "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()
            },
            "success": function (data) {
                var previousVersionId = data.d.Versions.results[1].VersionId;
                var currentVerionsId = data.d.Versions.results[0].VersionId;
                var previous = null;
                var current = null;
                spjs.highlightChanges.getVersion(_spPageContextInfo.pageListId, spjs.dffs.data.thisItemID, previousVersionId).done(function (data) {
                    previous = data;
                    spjs.highlightChanges.compare(previous, current);
                });
                spjs.highlightChanges.getVersion(_spPageContextInfo.pageListId, spjs.dffs.data.thisItemID, currentVerionsId).done(function (data) {
                    current = data;
                    spjs.highlightChanges.compare(previous, current);
                });
            },
            "error": function (err) {
                console.log(err);
            }
        });
    },
    "getDisplayValue": function (fin, val) {
        var value = val === null || val === null ? "" : val;
        if (value !== "") {
            switch (spjs.dffs.fieldtype[fin]) {
                case "SPFieldDateTime":
                    value = new Date(val.split("T").join(" ")).toLocaleString();
                    break;
                case "SPFieldMultiChoice":
                    value = value.results.join("; ");
                    break;
                case "SPFieldLookupMulti":
                case "SPFieldUserMulti":
                    var arr = [];
                    jQuery.each(value.results, function (i, o) {
                        arr.push(o.LookupValue);
                    });
                    value = arr.join("; ");
                    break;
            }
        }
        return value;
    },
    "compare": function (po, co) {
        if (po !== null && co !== null) {
            // Fix internal names where _ is escaped
            var p = {};
            jQuery.each(po, function (fin, o) {
                p[fin.split("_x005f_").join("_")] = o;
            });
            var c = {};
            jQuery.each(co, function (fin, o) {
                c[fin.split("_x005f_").join("_")] = o;
            });
            var changes = [];
            jQuery.each(spjs.dffs.fieldData, function (fin, o) {
                if (fin === "Proforma_x0020_Available_x0020_D") { }
                var pv = JSON.stringify(p[fin]);
                if (typeof pv === "string") {
                    pv = pv.replace(/[^a-z0-9\s]/gi, '');
                }
                var cv = JSON.stringify(c[fin]);
                if (typeof cv === "string") {
                    cv = cv.replace(/[^a-z0-9\s]/gi, '');
                }
                if (pv !== cv) {
                    changes.push({
                        "fin": fin,
                        "disp": o.disp,
                        "previous": spjs.highlightChanges.getDisplayValue(fin, p[fin]),
                        "current": spjs.highlightChanges.getDisplayValue(fin, c[fin]),
                        "previousEditor": p.Editor.LookupValue,
                        "currentEditor": c.Editor.LookupValue,
                        "currentModified": new Date(c.Modified.split("T").join(" ")).toLocaleString(),
                        "previousModified": new Date(p.Modified.split("T").join(" ")).toLocaleString()
                    });
                }
            });
            spjs.highlightChanges.__spjsChanges = changes;
            jQuery.each(changes, function (i, o) {
                jQuery("#dffs_" + o.fin).find(">td").css(spjs.highlightChanges.settings.highlightStyle);
                jQuery("#dffs_" + o.fin).find("td.ms-formbody").prepend("<img title='" + spjs.highlightChanges.settings.iconTooltip + "' style='float:right;margin-right:-25px;cursor:help;' src='" + spjs.highlightChanges.settings.iconSrc + "' onclick='spjs.highlightChanges.view(\"" + i + "\")'>");
            });
        }
    },
    "view": function (index) {
        var c = spjs.highlightChanges.__spjsChanges[index];
        spjs.modal.add({
            "title": spjs.highlightChanges.settings.dlgTitle + " " + c.disp,
            "html": "<strong>" + spjs.highlightChanges.settings.currentVersionLabel + "</strong> [" + spjs.highlightChanges.settings.modifiedLabel + c.currentModified + spjs.highlightChanges.settings.modifiedByLabel + c.currentEditor + "]<br>" + JSON.stringify(c.current, null, 4) + "<hr style='margin:10px 0'><strong>" + spjs.highlightChanges.settings.previousVersionLabel + "</strong> [" + spjs.highlightChanges.settings.modifiedLabel + c.previousModified + spjs.highlightChanges.settings.modifiedByLabel + c.previousEditor + "]<br>" + JSON.stringify(c.previous, null, 4),
            "ok": function () {
                // Close
            }
        });
    },
    "getVersion": function (listId, listItemId, verionsId) {
        var deferred = jQuery.Deferred();
        jQuery.ajax({
            "url": _spPageContextInfo.webAbsoluteUrl + "/_api/Web/Lists/getbyid('" + listId + "')/items('" + listItemId + "')/versions('" + verionsId + "')",
            "method": "GET",
            "headers": {
                "accept": "application/json; odata=verbose",
                "content-type": "application/json;odata=verbose",
                "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()
            },
            "success": function (data) {
                deferred.resolve(data.d);
            },
            "error": function (err) {
                console.log(err);
            }
        });
        return deferred.promise();
    }
};

spjs.highlightChanges.init();

You can change the settings in the top of the script to change the text and styling of the highlight.

Please post any questions below or in the forum.

Alexander

Leave a Reply

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