0

can anyone help me out with the code below. I'm attempting to update a list item on save. I believe I would do this with the presaveaction function. Can you please tell me where I'm going wrong. I'm not too experienced with coding, but have been piecing some things together.

<script language="javascript" type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(updateListItem,'sp.js'); 

function PreSaveAction()
{   
   
      alert('Going to update!');
      updateListItem();
      alert('Updated!');   
}

function updateListItem() {
var clientContext = new SP.ClientContext.get_current();
var list = clientContext.get_web().get_lists().getByTitle('AutoQuoteNumberTest');
var listItem = list.getItemById(1);
listItem.set_item('NextQuoteNum_x0020_Available', '8888');
listItem.update();
}

function onQuerySucceeded() {
alert('Loaded !');
}

function onQueryFailed(sender, args) {
alert('Could not able to update item: ' + args.get_message());
}
</script>

Second attempt to piece it all together. I am trying to use a column from the list instead of a hard coded number...

enter image description here

<script src="https://code.jquery.com/jquery-2.2.4.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript">

 var clientContext = null;
 var web = null;
 ExecuteOrDelayUntilScriptLoaded(AutoGenerate, "sp.js");
    
 function AutoGenerate() {
 
     var targetListItem;
     clientContext = new SP.ClientContext.get_current();
     web = clientContext.get_web();
     var list = web.get_lists().getById(_spPageContextInfo.pageListId);
     var camlQuery = new SP.CamlQuery();
     camlQuery.set_viewXml('<View><Query></Query></View>');
     this.listItems = list.getItems(camlQuery);
     clientContext.load(listItems);
     clientContext.executeQueryAsync(Function.createDelegate(this, this.onListItemsLoadSuccess),
         Function.createDelegate(this, this.onQueryFailed));
 }
    
 function onListItemsLoadSuccess(sender, args) {
     var itemId = 1;  
     var clientContext = new SP.ClientContext(); 
     var targetList = clientContext.get_web().get_lists().getByTitle('AutoQuoteNumberTest');
     targetListItem = targetList.getItemById(itemId);
     
     clientContext.load(targetListItem, 'NextQuoteNumber');
     
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
     
 }

function updateListItem() {
var clientContext = new SP.ClientContext.get_current();
var list = clientContext.get_web().get_lists().getByTitle('AutoQuoteNumberTest');
var listItem = list.getItemById(1);
clientContext.load(listItem, 'NextQuoteNumber','NextQuoteNum_x0020_Available','Next_x0020_Number');

var nextnumber = listItem.get_item('Next_x0020_Number');
var lastnumber = listItem.get_item('NextQuoteNum_x0020_Available');
alert(nextnumber);
alert(lastnumber);
listItem.set_item('NextQuoteNum_x0020_Available', nextnumber);
listItem.update();
clientContext.executeQueryAsync(onQuerySucceeded, onQueryFailed);
}

function PreSaveAction()
{   
   
      alert('Going to update!');
      updateListItem();
      alert('Updated!');   
return true;
}

 function onQuerySucceeded() {
     var AutoIncremental = targetListItem.get_item('NextQuoteNumber');
     

     $("input[title^='Quote Number']").val(AutoIncremental);
     $("input[title^='Quote Number']").attr('disabled', 'disabled');
   }
 function onQueryFailed(sender, args) {
     alert('request failed ' + args.get_message() + '\n' + args.get_stackTrace());
 }
    

</script>

2 Answers 2

0

You're going to have a lot of timing issues with this and you probably don't want to do it in PreSaveAction, actually.

What it seems like you are tying to do is set something up to work like this:

  1. User opens the form.
  2. Your code checks the auto number list to get what should be the next quote number (but does not update the auto number list yet).
  3. You populate the quote number field in the current form with the next number.
  4. The user fills out the form.
  5. The user clicks Save.
  6. Your code increments and saves the auto number list item in the other list.
  7. The form is saved.

But let's look at the timing problem with that approach:

  1. User Adam opens the form.
  2. Your code checks the auto number list and gets the next quote number, which happens to be 101.
  3. You populate Adam's form's quote number field with 101.
  4. Adam decides to go get a cup of coffee.
  5. On a different computer, user Barbara opens the form.
  6. Your code checks the auto number list and gets the next quote number, which is still 101.
  7. You populate Barbara's quote number field with 101.
  8. Barbara fills out the form and clicks Save.
  9. Your code increments the auto number list and sets the next number as 102.
  10. Barbara's form is saved with quote number 101.
  11. Adam gets back with his coffee and fills out the form, and clicks Save.
  12. Your code increments from Adam's 101 and sets the next number as 102.
  13. Adam's form is saved, also with quote number 101.

A safer, but not entirely risk-free way to do it would be to get then next quote number when the form loads by using ExecuteOrDelayUntilScriptLoaded so you can use the client context as soon as it's ready, and at the same time increment and save the next number available, and never worry about incrementing it using PreSaveAction when the user clicks Save.

  1. Adam opens the form.
  2. Your code gets the next quote number from the other list (101), and immediately increments it (to 102) and updates that auto number list item.
  3. You populate Adam's form with quote number 101.
  4. Adam gets a phone call and is distracted.
  5. Barbara opens the form.
  6. Your code gets the next quote number, which is now already set as 102, and increments it to 103.
  7. You populate Barbara's form with quote number 102.
  8. Barbara saves her form with quote number 102.
  9. Adam eventually saves his form with quote number 101.

There's two risks with that approach:

  • It's possible to drop/lose quote numbers, they are not guaranteed to be continuous. For instance, if Adam gets quote number 101 by opening the form, increments the next to 102, then Barbara gets 102 and increments the next to 103, what happens if Adam cancels the form and doesn't save it? There's no item saved that has quote number 101, they would skip from 100 to 102. If that's not a big deal than this is a much better solution.
  • There is a very, very small chance that you could still have a timing issue. If Adam and Barbara both open the form at nearly exactly the same time, it is possible that because of variances in network traffic etc., both of the requests for the next number go through and return the same number before either one of them is able to increment and update the next number. This would be incredibly unlikely, but it is possible.

All that being said,

if you do not want gaps in the quote numbers and only ever want to increment the next quote number when someone clicks Save and commits to submitting the form, you can retrieve the next number from the other list, populate that into the form, and then increment and save the next number in the other list, all from PreSaveAction, but it gets very tricky because PreSaveAction is designed to be a synchronous function, and all that retrieving and updating is done asynchronously.

Also, if you do it from PreSaveAction, you obviously can't pre-populate the form with the quote number so the person won't see what quote number they are going to be assigned.

In order to control the execution flow and trigger some async functionality from PreSaveAction and wait for the results to return before allowing the form to save, you have to set a flag variable to know when your async operations are done, and then re-trigger the Save button click from within your own code to re-start the form submission process.

I see you're using jQuery, so I will use that to kind of show you what you can do, but obviously this is kind of pseudo code just to show you an example and the selectors etc. are not accurate. Also I'm rusty on my JSOM so that might be a little off too, but you will get the idea.

// set up a flag variable to keep track of whether or not
// you finished all your async actions
var nextQuoteNumWasUpdated = false;

// set this up as a global variable so that you
// don't have to worry about passing it around
// between callback functions
var quoteNumListItem = null;

function PreSaveAction() {
    if (!nextQuoteNumWasUpdated) {
        // you haven't retrieved or incremented the next quote number yet

        // disable the save button so people don't accidentally click twice
        $('#SaveButton').prop('disabled', true);

        // start that async process
        retrieveNextQuoteNumber();

        // you cannot wait for an async process to finish here,
        // it will not block the code execution, so you have to
        // allow PreSaveAction to finish, but you don't want
        // the form saved yet, so tell it it can't save yet
        // by returning false
        return false;

    } else {
        // the flag was set to true, so you 
        // have successfully retrieved the next quote number,
        // populated it in this form, and
        // incremented and updated the next quote number,
        // so allow the form to save by returning true
        return true;
    }
}

function retrieveNextQuoteNumber() {
    var clientContext = new SP.ClientContext.get_current();
    var list = clientContext.get_web().get_lists().getByTitle('AutoQuoteNumberTest');
    quoteNumListItem = list.getItemById(1);
    clientContext.load(quoteNumListItem, 'NextQuoteNumber');

    // send the async request to the server and specify your callback functions
    clientContext.executeQueryAsync(retrieveNextNumSucceeded, retrieveNextNumFailed);
}

function retrieveNextNumSucceeded() {
    // get the number from the item you retrieved
    var nextQuoteNumber = quoteNumListItem.get_item('NextQuoteNumber');
    
    // set the value in your form
    $("input[title^='Quote Number']").val(nextQuoteNumber);

    // increment the number and update the other list
    var incrementedNumber = nextQuoteNumber + 1;
    var clientContext = new SP.ClientContext.get_current();
    var list = clientContext.get_web().get_lists().getByTitle('AutoQuoteNumberTest');
    var listItem = list.getItemById(1);
    listItem.set_item('NextQuoteNumber', incrementedNumber);
    listItem.update();

    // send the async request to the server and specify your callback functions
    clientContext.executeQueryAsync(updateNextNumSucceeded, updateNextNumFailed);
}

function retrieveNextNumFailed() {
    alert('Failed to retrieve the next quote number: ' + args.get_message());
}

function updateNextNumSucceeded() {
    // update was successful, so you are all
    // done with your async operations, so now you
    // can allow the form to save.

    // first, set your flag so that PreSaveAction knows you are done
    nextQuoteNumWasUpdated = true;

    // now, re-trigger the Save button to finally save the form
    $('#SaveButton').prop('disabled', false);
    $('#SavebUtton').click();
}

function updateNextNumFailed(sender, args) {
    alert('Could not able to update item: ' + args.get_message());
}
2
  • Thank you for the very long response. Those are good points! I previously used a workflow with sharepoint on prem, but the delay with sharepoint online and flow is too long. we never ran into timing issues previously which i'm surprised, but are now with the delay with sharepoint online flows. I'm probably better off just using the built in ID field... but this has been fun to experiment with. I will try your approach and suggestions.
    – Tyler
    Jul 15 at 19:38
  • Dylan, Your solution worked perfectly. The only addition I had to make was to disable the quote number field on my form from text entry. Thank you so much!
    – Tyler
    Jul 15 at 22:37
0

You're missing an important line of code in the updateListItem function.

function updateListItem() {
  var clientContext = new SP.ClientContext.get_current();
  var list = clientContext.get_web().get_lists().getByTitle('AutoQuoteNumberTest');
  var listItem = list.getItemById(1);
  listItem.set_item('NextQuoteNum_x0020_Available', '8888');
  listItem.update();

  // Need to add this line
  clientContext.executeQueryAsync(onQuerySucceeded, onQueryFailed);
}

The call to executeQueryAsync tells the Client Object Model to execute the update to the list item. It also attaches the success and fail callbacks.

2
  • Thank you, that worked. See my next response to help me piece it all together. My next action was to use a list column to set the field. It seems to be failing.
    – Tyler
    Jul 15 at 15:24
  • I updated my original question with the full code I am trying to use. If I leave it hardcoded with a number it works. What I am trying to do is use a retrieved column to set the item. I hope that makes sense. It seems like I am missing something simple.
    – Tyler
    Jul 15 at 15:29

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.