This was inspired by Paul Crawford's post (huge thanks to you) Ensure Unique Reference On New Item, which was incredibly enlightening. And while it was almost the perfect solution for what I was looking to do (generating an incremental number that was unique and guaranteed), there were a few instances when several new forms could be created with the same value.
I attempt to solve this problem by leveraging the power of an Item's ID field, which as far as I know, cannot be tampered with, and automatically increments upon creation. Additionally, because SharePoint proper is handling the creation and destruction of these Items, it seems perfectly capable of sidestepping any concurrency issue that would arise.
So, without further ado...
My Form Information:
I'm using a tiny test form that has a Title field control, and a few other controls with custom widgets on them.
Setup:
- You'll wanna setup the control that targets the column you'll be generating an incremental value for. In this example, I'm using the Title variable.
- To make life easier, create a unique name for the Control's ID value by doing the following. I've used the name 'uniqueTitleTarget' for this example.
- Afterwards, we'll need to create a Rule that we will use to disable the control once we've pushed a value to it.
The code used for the rule is:not(isNullOrEmpty({Self}))
- Enter the Form Settings dialog by using the button found in the Ribbon Menu.
- From there, access the Custom Javascript text area, and insert the following code:
NWF.FormFiller.Events.RegisterAfterReady(function() {
NWF$(document).ready(function() {
ExecuteOrDelayUntilScriptLoaded(createNewNumber, "sp.js");
});
});
function createNewNumber() {
var targetControl = NWF$("#" + uniqueTitleTarget);
if (!targetControl.val()) {
targetControl.attr("disabled", true);
var controlValuePrefix = "Quote Number - ";
var createdIDNumber = 0;
var constantValue = 5000;
var controlValueNumber;
var newControlValue;
createListItem();
function createListItem() {
var clientContext = new SP.ClientContext(_spPageContextInfo.webAbsoluteUrl);
var oList = clientContext.get_web().get_lists().getByTitle("Quote Number List");
this.oListItem = oList.addItem();
oListItem.update();
clientContext.load(oListItem);
clientContext.executeQueryAsync(
Function.createDelegate(this, onQuerySucceeded),
Function.createDelegate(this, onQueryFailed));
}
function deleteListItem(sentItemID) {
var clientContext = new SP.ClientContext(_spPageContextInfo.webAbsoluteUrl);
var oList = clientContext.get_web().get_lists().getByTitle("Quote Number List");
this.oListItem = oList.getItemById(sentItemID);
clientContext.load(oListItem);
oListItem.deleteObject();
clientContext.executeQueryAsync(
Function.createDelegate(this, onDeleteSucceeded),
Function.createDelegate(this, onQueryFailed)
);
}
function onQuerySucceeded() {
if (oListItem.get_id() > 0) {
createdIDNumber = oListItem.get_id();
controlValueNumber = createdIDNumber + constantValue;
newControlValue = controlValuePrefix + controlValueNumber;
targetControl.val(newControlValue);
targetControl.attr("disabled", false);
NWF.FormFiller.Functions.ProcessOnChange(targetControl);
alert("Item CREATED!" +
"\n" +
"ID = " +
oListItem.get_id() +
"\n" +
"\n" +
"Your current item value = " +
createdIDNumber);
deleteListItem(oListItem.get_id());
}
}
function onDeleteSucceeded() {
alert("Item DELETED!" +
"\n" +
"ID = " +
oListItem.get_id() +
"\n" +
"\n" +
"Your current item value still = " +
createdIDNumber);
}
function onQueryFailed(sender, args) {
alert("Request failed. " +
args.get_message() +
"\n" +
args.get_stackTrace());
}
}
}
When you're finished, it should look something like this:
- Save your work, and Preview it using the Preview button located in the Ribbon Menu
- Once your form has finished loading, you will see this Alert box informing you that an item has been created!
- After clicking on the OK button, you will see another Alert informing you that the item has been successfully destroyed!
- If everything has worked correctly, our Title Control should now be populated with a new unique value that we'll use when we create the Item.
And now you're finished!
But what about all of that code? What does it do???
Glad that you asked! Below I have included a fully commented version of the above code that explains in detail what's happening. Again, a lot of this stuff was totally new to me, and had it not been for Paul Crawford (mentioned above), I would have never even made it this far.
// We want to invoke our code after the form has loaded,
// and using this built in function, we can register it
// to run immediately after that happens
NWF.FormFiller.Events.RegisterAfterReady(function() {
// Regster an event that will fire once the document is
// finished rendering
NWF$(document).ready(function() {
// Using a function provided with the SP.SOD library
// we'll invoke our function AFTER we check that the
// sp.js file has loaded (because we're relying on a
// few functions that it exposes)
ExecuteOrDelayUntilScriptLoaded(createNewNumber, "sp.js");
});
});
function createNewNumber() {
// To make life easier, we can also define our Target Control using the
// javascript variable name that we setup for the ID of that control
var targetControl = NWF$("#" + uniqueTitleTarget);
// We don't wanna populate our control with a value IF it already has one!
// Otherwise it could overwrite something while doing something like
// editing, which would make us very sad
if (!targetControl.val()) {
// While we have created a Form Rule to disable to field, we can make
// sure that it doesn't get tampered with beforehand by disabling it
// manually right here
targetControl.attr("disabled", true);
// In the event that you'd like the field / column that you're populating
// to be something more than a number. You can set this variable
// to the prefix that you'd like to tack onto the front
var controlValuePrefix = "Quote Number - ";
// Let's set up a variable that will hold the results of our
// newly created number.
var createdIDNumber = 0;
// Maybe you don't want your numbers to start incrementing from
// whatever the ID is, and would like to add a value to that
// for a baseline. You can set that here (replacing the 0), or
// create a constantValue column in the target list as I have below.
var constantValue = 5000;
// We'll also set up an empty variable that will hold our incremental
// number result (createdIDNumber + constantValue)
var controlValueNumber;
// The final combination of all of our work will be stored in a variable
// called newControlValue
var newControlValue;
// All we're doing here is invoking the createListItem function
// after everything has been declared
createListItem();
// ***************************************
// **** FUNCTION START createListItem ****
// ***************************************
// This function will be used to make a new Item in our target List
function createListItem() {
// set your Client Context to the Site of that contains the list you're targeting
//
// Tiny Example:
//
// If you had a root Site Collection called MyCollection, but you were RUNNING
// this from inside of a Child Site called "Sandbox" here are various outputs you
// would expect to see from a few of the _spPageContextInfo methods
//
// *********************
//
// _spPageContextInfo.serverRequestPath = "/sites/MyCollection/Sandbox/Lists/Quote Number List/AllItems.aspx"
//
// _spPageContextInfo.siteAbsoluteUrl = "http://sptest2016/sites/MyCollection"
//
// _spPageContextInfo.siteServerRelativeUrl = "/sites/MyCollection"
//
// _spPageContextInfo.webAbsoluteUrl = "http://sptest2016/sites/MyCollection/Sandbox"
//
// _spPageContextInfo.webServerRelativeUrl = "/sites/MyCollection/Sandbox"
//
// *********************
// Getting our current Context
var clientContext = new SP.ClientContext(_spPageContextInfo.webAbsoluteUrl)