Category Archives: DFFS

Break permission inheritance on a new list item and assign edit rights to users selected in a people picker

I got a request for a solution to break inheritance of permissions on a new list item and assign edit rights to users selected in a people picker.

IMPORTANT INFORMATION:

Unfortunately users must have Manage Permissions rights to be able to use this code. You must assign this right to the users by going to Site settings > Site permissions > Permission levels > Edit permission level > Site permissions > Manage permissions. Do this with caution as it will give general Mange permission rights if you don’t add a custom permission level and assign it to a specific SharePoint user group.

This example requires a list with a multichoice people picker where you select the users to assign edit item permissions – the name of this people picker is in this code is EditRightsPeoplePicker, but you can specify the name in the bottom of the code snippet.

Add the below snippet to the Custom JS textarea of your DFFS Enabled NewForm. When saving the item the default save function will be cancelled and the custom function will create an item, break permission inheritance and assign edit rights to the users selected in the people picker. In addition, the current user will get full control over the list item.

Please note that this is not a complete fully functional solution – it is only a proof of concept and you can use it as a starting point for making your own solution.

// Don't edit this function
 function breakroleinheritance(arg) {
     var deferred = jQuery.Deferred();
     jQuery.ajax({
         "url": arg.listBaseUrl + "/_api/web/lists/getbyid('" + arg.listGuid + "')/items(" + arg.itemId + ")/breakroleinheritance(false)",
         "type": 'POST',
         "headers": {
             'X-RequestDigest': jQuery('#__REQUESTDIGEST').val(),
             "accept": "application/json;odata=verbose",
             "content-type": "application/json;odata=verbose"
         },
         "success": function(data) {
             deferred.resolve(true);
         },
         "error": function(err) {
             console.log(err);
             deferred.reject(err);
         }
     });
     return deferred.promise();
 }
 // Don't edit this function
 function doAssignNewRole(arg) {
     var deferred = jQuery.Deferred(),
         ticker = 1;
     jQuery.each(arg.roles, function(i, r) {
         jQuery.ajax({
             "url": arg.listBaseUrl + "/_api/web/lists/getbyid('" + arg.listGuid + "')/items(" + arg.itemId + ")/roleassignments/addroleassignment(principalid=" + r.principalid + ",roledefid=" + r.roledefid + ")",
             "type": 'POST',
             "headers": {
                 'X-RequestDigest': jQuery('#__REQUESTDIGEST').val()
             },
             "success": function(data) {
                 if (ticker === arg.roles.length) {
                     deferred.resolve(true);
                 } else {
                     ticker += 1;
                 }
             },
             "error": function(err) {
                 console.log(err);
                 deferred.reject(err);
             }
         });
     });
     return deferred.promise();
 }
 // Don't edit this function
 function setItemLevelSecurity(arg) {
     var deferred = jQuery.Deferred();
     breakroleinheritance(arg).done(function() {
         doAssignNewRole(arg).done(function(success) {
             deferred.resolve(arg.itemId);
         }).fail(function(err) {
             spjs.dffs.alert({
                 "title": "Add roles error",
                 "msg": JSON.stringify(err)
             });
         });
     });
     return deferred.promise();
 }
 // Don't edit this function
 function init_setItemLevelSecurity(arg) {
     var deferred = jQuery.Deferred();
     var users = spjs.utility.getFieldValue({
         "fin": arg.peoplePicker,
         "key": "loginName"
     });
     var roleArr = [],
         userIdArr = [];
     jQuery.each(users, function(i, login) {
         var userId = spjs.utility.userInfo(login).ID;
         roleArr.push({
             "principalid": userId,
             "roledefid": "1073741830" // 1073741830 = edit
         });
         userIdArr.push(userId);
     });
     // Create new item
     var newItemData = {};
     newItemData.Title = getFieldValue("Title");
     newItemData[arg.peoplePicker] = userIdArr.join(";#;#");
     var res = spjs.utility.addItem({
         "listName": _spPageContextInfo.pageListId,
         "data": newItemData
     });
     arg.roles = roleArr;
     if (res.success) {
         arg.itemId = res.id;
         setItemLevelSecurity(arg).done(function(id) {
             deferred.resolve(id);
         });
     }
     return deferred.promise();
 }
 function dffs_PreSaveAction() {
     createNewItemWithItemLevelSecurity();
     return false;
 }
 // Edit this function
 function createNewItemWithItemLevelSecurity() {
     init_setItemLevelSecurity({
         "listBaseUrl": _spPageContextInfo.webServerRelativeUrl,
         "listGuid": _spPageContextInfo.pageListId,
         "peoplePicker": "EditRightsPeoplePicker"
     }).done(function(itemId) {
         // Done
         location.href = location.href.split("NewForm.aspx")[0] + "EditForm.aspx?ID=" + itemId;
     });
 }

Please post any questions in the comments below.

Alexander

Keep track of assigned and unassigned keys for office locations

I got a request for a solution to solve the following question:

I break down the columns in the two lists below. What I’m trying to accomplish is to get the “key designation” field in the key log list to drive the number of assigned, unassigned and total keys in the inventory. For example, if “assign” is chosen in the key designation field of the key log, it automatically increases the number of keys assigned by one key, while decreasing the number of keys unassigned. If “return” is selected, it automatically decreases the number of assigned keys and increases the number of unassigned keys. The inventory holds information detailing keys across two different locations/offices. The columns in the inventory are:

  • Site (Boston, New York, etc.) 
  • Protected Area (the area to which the keys provide access)
  • Identifier (corresponding symbol on the key that ties that key to the protected area [identifier x = broom closet protected area for example])
  • Number of Keys Assigned (5)
  • Number of Keys Unassigned (4)
  • Total Keys (sum of the number of assigned and unassigned keys [9])

The Key Log tracks when a key is assigned, returned, added or deleted and who assigned the key (with 2 person or group fields to account for dual control). The columns in the log are:

  • Site
  • Protected Area
  • Identifier
  • Key number
  • key designation (if we’re assigning, returning, adding or deleting a key)
  • person or group #1 (to show who assigned the key)
  • person or group #2 (to show dual control for 2nd person assigning the key)

I have created two lists with the essential fields to make the relationship like this:

inventory list

The TotKeys is a calculated column that sums the NumberAssigned and NumberUnassigned.

keylog list

The KeyDesignation field has these choices:

assigning
returning
adding
deleting

Now install DFFS in the keylog list and set up a cascading dropdown like this:

Cascading dropdown config

Then add this in your Custom JS of both NewForm and EditForm in the keylog list:

When adding or editing a keylog item the corresponding item in the inventory will be updated with the correct count.

// Reset KeyDesignation field on load
setFieldValue("KeyDesignation", "");

function dffs_PreSaveAction() {
    var site = getFieldValue("Site");
    var protectedArea = getFieldValue("ProtectedArea");
    var inventoryItem = spjs.utility.queryItems({
        "listName": "inventory",
        "query": "<Where><And><Eq><FieldRef Name='Site' /><Value Type='Text'>" + site + "</Value></Eq><Eq><FieldRef Name='ProtectedArea' /><Value Type='Text'>" + protectedArea + "</Value></Eq></And></Where>",
        "viewFields": ["ID", "NumberAssigned", "NumberUnassigned"]
    });
    var pass = true;
    if (inventoryItem.count > 0) {
        var item = inventoryItem.items[0];
        var assigned = item.NumberAssigned !== null ? parseInt(item.NumberAssigned, 10) : 0;
        var unassigned = item.NumberUnassigned !== null ? parseInt(item.NumberUnassigned, 10) : 0;
        var keyDesignation = getFieldValue("KeyDesignation");
        switch (keyDesignation) {
            case "assigning":
                if (unassigned > 0) {
                    assigned += 1;
                    unassigned -= 1;
                } else {
                    pass = false;
                    spjs.dffs.alert({
                        "title": "No keys available",
                        "msg": "You are trying to assign a key, but no unassigned keys are available.",
                        "ok": function() {
                            // Close dlg
                        }
                    });
                }
                break;
            case "returning":
                if (assigned > 0) {
                    assigned -= 1;
                    unassigned += 1;
                } else {
                    pass = false;
                    spjs.dffs.alert({
                        "title": "No keys assigned",
                        "msg": "You are trying to unassign a key, but no keys are assigned.",
                        "ok": function() {
                            // Close dlg
                        }
                    });
                }
                break;
            case "adding":
                unassigned += 1;
                break;
            case "deleting":
                unassigned -= 1;
                break;
        }
        // Update list item with new count
        spjs.utility.updateItem({
            "listName": "inventory",
            "id": item.ID,
            "data": {
                "NumberAssigned": assigned,
                "NumberUnassigned": unassigned
            }
        });
    }
    if (pass) {
        return true;
    } else {
        return false;
    }
}
keylog list NewForm

Let me know in the comments below if you have any questions.

Alexander

Add a note to self textarea in a DFFS form

I got a request:

Hi Alexander,
I hope you are doing well. I’m wondering if you’ve done anything like this before: a field or area on an item where someone can add their own notes not visible to anyone except the logged in user. I thought maybe a rule but what if one person has their notes and someone else has their own? Thanks in advance for any advice on how you’d handle it.

This can be done with a few lines of Custom JS as shown below. This method stores the text in localStorage in the browser. Please note that this requires the localStorage to be activated (it is by default, but it can be turned off).

You must also add a field named _DFFSID to your form to hold the automatically generated unique itemID used to identify the values.

Add this to a table row added in your DFFS tab:

<textarea id="dffs_note_to_self" style="height:75px;width:100%;box-sizing:border-box;" onkeyup="saveToLocalStorage(this)"></textarea>

Then add this to your Custom JS:

function saveToLocalStorage(elm){
    var val = jQuery(elm).val();
    var id = getFieldValue("_DFFSID");
    localStorage.setItem(id,escape(val));
}

function dffs_ready(){
    var id = getFieldValue("_DFFSID");
    var noteToSelf = localStorage.getItem(id);
    if(noteToSelf !== null){
        jQuery("#dffs_note_to_self").val(unescape(noteToSelf));
    }
}

You can add this to NewForm, DispForm and EditForm and the text you enter will show every time you open it – like this:

The text is automatically saved when you type the value in the textarea, but because it uses the localStorage in the browser to store the text it is not saved in the list item and is only available if you open the item in the same browser on the same computer as you used to type the text in.

Let me know in the comments how this works out.

Best regards,
Alexander

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