N M

Creating An Incremental Number That Avoids Concurrency Problems

Blog Post created by N M on Feb 15, 2017

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

  1.  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. 

    Figure 01. Targeting the Title Control

  2. 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.

    Figure 02. Creating The ID Variable

  3. Afterwards, we'll need to create a Rule that we will use to disable the control once we've pushed a value to it.

    Figure 03. Creating Our Control's Rule
    The code used for the rule is: 
    not(isNullOrEmpty({Self}))

     

  4. Enter the Form Settings dialog by using the button found in the Ribbon Menu.

    Figure 04. Entering The Settings Dialog

  5. 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: 

    Figure 05. Custom Javascript Entered


  6. Save your work, and Preview it using the Preview button located in the Ribbon Menu

    Figure 06. Generating A Preview
  7. Once your form has finished loading, you will see this Alert box informing you that an item has been created!

    Figure 07. Alert! Item Has Been Created!
  8. After clicking on the OK button, you will see another Alert informing you that the item has been successfully destroyed!

    Figure 08. Alert! Item Destroyed!
  9. 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.

Figure 09. Resulting Unique Value

 

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);

      // Get our target List
      // In this instance, it is a List called 'Quote Number List'
      var oList = clientContext.get_web().get_lists().getByTitle("Quote Number List");

      // We'll create an empty item
      this.oListItem = oList.addItem();

      // In order to commit this new item, we'll have to update it
      oListItem.update();

      // In order to reference the oListItem's properties, we'll need to
      // load it into our current Context after we've submitted our query
      clientContext.load(oListItem);

      // Now we'll submit our query to SharePoint and see if we've succeeded
      // or if we've failed terribly and must hang our heads, forever in shame
      clientContext.executeQueryAsync(
        Function.createDelegate(this, onQuerySucceeded),
        Function.createDelegate(this, onQueryFailed)
      );
    }

    // ***************************************
    // **** FUNCTION END createListItem ****
    // ***************************************



    // ***************************************
    // **** FUNCTION START deleteListItem ****
    // ***************************************

    // We'll use this function to delete an Item from SharePoint
    // It requires one argument, an ID number, that will be used
    // to target an item in a list
    function deleteListItem(sentItemID) {

      // Getting our current Context
      var clientContext = new SP.ClientContext(_spPageContextInfo.webAbsoluteUrl);

      // Get our target List
      var oList = clientContext.get_web().get_lists().getByTitle("Quote Number List");

      // Get our target List Item by way of the sentItemID value
      this.oListItem = oList.getItemById(sentItemID);

      // If our deletion succeeds, we'll want to reference the Item deleted
      // to display some information to the user.
      // Because of that, we'll load the Item before we delete it.
      // If you aren't going to do anything with the oListItem property
      // after this point. Feel free to delete this line of code!
      clientContext.load(oListItem);

      // Invoke the Delete method on our List Item
      oListItem.deleteObject();

      // Execute the above statements in SharePoint
      // If it succeeds, we'll run the onDeleteSucceeded function,
      // otherwise, we'll run onQueryFailed
      clientContext.executeQueryAsync(
        Function.createDelegate(this, onDeleteSucceeded),
        Function.createDelegate(this, onQueryFailed)
      );
    }

    // ***************************************
    // **** FUNCTION END deleteListItem ******
    // ***************************************


    // ***************************************
    // **** EVENT HANDLERS START *************
    // ***************************************

    // This is the function that will execute, assuming our query succeeds
    function onQuerySucceeded() {

      if (oListItem.get_id() > 0) {

        // We'll set the value of our createdIDNumber variable to the
        // ID of the item we've created
        createdIDNumber = oListItem.get_id();

        // We'll set the value of our controlValueNumber variable
        // to the value of ID number + our constantValue (which is 5000
        // in this case)
        controlValueNumber = createdIDNumber + constantValue;

        // We'll finally set the value of our newControlValue
        //
        // Assuming that:
        //
        // controlValuePrefix = "Quote Number - "
        // createdIDNumber = 1
        // constantValue = 5000
        //
        // The resulting newControlValue should = "Quote Number - 5001"
        newControlValue = controlValuePrefix + controlValueNumber;

        // Now we'll set the value of our Target Control to our
        // newly created value!
        targetControl.val(newControlValue);

        // Before we force our disabling Form Rule to run, we need
        // to remove the manual disabled state that we applied
        // to the control, otherwise bad things might happen
        targetControl.attr("disabled", false);

        // We'll also need to run an update function that will
        // subsequently run our validation rule, disabling
        // the control down to prevent changes to the field
        NWF.FormFiller.Functions.ProcessOnChange(targetControl);


        // You'll probably want to change (delete) this, but
        // for a test, we'll display the value of createdIDNumber
        alert("Item CREATED!" +
          "\n" +
          "ID = " + oListItem.get_id() +
          "\n" +
          "\n" +
          "Your current item value = " + createdIDNumber);

        // Now that we have a new value
        // we can just delete the Item by invoking the
        // deleteListItem function with our newly created
        // Item's id as the passed argument
        deleteListItem(oListItem.get_id());
      }
    }

    function onDeleteSucceeded() {

      // Again. You'll probably want to delete this, but if we're
      // successfully able to delete the item, we'll display
      // some information about the Item deleted
      alert("Item DELETED!" +
        "\n" +
        "ID = " + oListItem.get_id() +
        "\n" +
        "\n" +
        "Your current item value still = " + createdIDNumber);
    }

    function onQueryFailed(sender, args) {

      // If for whatever reason we fail to create or delete
      // an Item. this message will display.
      alert("Request failed. " +
        args.get_message() +
        "\n" +
        args.get_stackTrace());
    }

    // ***************************************
    // **** EVENT HANDLERS END ***************
    // *************************************** 
  }

}

 

Additionally

 

If you're interested in learning more about some of this stuff, here are a few of the resources that I used to help my brain. 

I never realized that there was a wonderful little SharePoint javascript library included with SharePoint by default, but now that I do, I am a happier person. 

 

Learn about the SP Namespace: SP Namespace 

 

Learn about the SP.SOD library : SP.SOD Class 
(which brings us useful functions such as ExecuteOrDelayUntilScriptLoaded)

 

Learn about Items interaction using javascript: How to: Create, Update, and Delete List Items Using JavaScript 

(which teach you exactly what you think! How to create, update, and delete things!

 

Finally

 

If you want this code without the unnecessary testing stuff (alerts), here you are: 

 

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);
      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);
        deleteListItem(oListItem.get_id());
      }
    }

    function onDeleteSucceeded() {

    }

    function onQueryFailed(sender, args) {
      alert("Request failed. " +
        args.get_message() +
        "\n" +
        args.get_stackTrace());
    }
  }
}

 


I hope that this helps anyone else who was stuck with the same problem! 

Outcomes