1

I'm in a bit of a quandry as I'm just learning how to do development in SharePoint 2016 / SharePoint Online and using Javascript to perform REST calls. I have a form that is using REST to successfully pull data from a SharePoint list using the following:

function getListItems(webURL, listName, successFunction, successParams, failFunction) { // REST Call $.ajax({ url: webURL + "/_api/web/lists/GetByTitle('" + listName + "')/items?$top=1000", type: "GET", headers: { "ACCEPT": "application/json;odata=verbose" }, success: function(data) { successFunction(data, successParams); }, error: function(error) { failFunction(error); } }); }; 

I also have the following to submit data to a single SharePoint list, which isn't quite working (it submits to the list, but calls the fail function). Could someone help me to understand why it is failing?

// Get List Item Type metadata function GetItemTypeForListName(name) { return "SP.Data." + name.charAt(0).toUpperCase() + name.split(" ").join("").slice(1) + "ListItem"; } function submitListItems(webURL, listName, jsonObj, successFunction, failFunction) { // REST Call to create new list item var jsonMeta = {'__metadata': {'type': GetItemTypeForListName(listName)}}; // Merge the two JSON lists to include SP metadata information jsonObj = $.extend(jsonMeta, jsonObj); $.ajax({ url: webURL + "/_api/web/lists/GetByTitle('" + listName + "')/items", type: 'POST', contentType: 'application/json;odata=verbose', data: JSON.stringify(jsonObj), headers: { 'ACCEPT': 'application/json;odata=verbose', 'X-RequestDigest': $('#__REQUESTDIGEST').val() }, success: function(data) { successFunction(data, jsonObj); }, error: function (error) { console.log(JSON.stringify(error)); failFunction(error); } }); }; 

The form I am working with is best designed to submit data to two separate lists, with the lists having a 1:Many relationship on a common field primary key that is created during the submit process for list1, and the lists would join on list1.pk_id_1 = list2.sk_id1 (if SharePoint could do list joins). Thus my two lists are set up as:

list1: PK_ID_1 item1 item2 item3 item4 ... PK_ID_2 item1 item2 item3 item4 ... PK_ID_3 item1 item2 item3 item4 ... list2: PK_1 SK_ID_1 item1a item2a item3a item4a PK_2 SK_ID_1 item1b item2b item3b item4b ... PK_3 SK_ID_1 item1c item2c item3c item4c ... PK_4 SK_ID_2 item1a item2a item3a item4a ... PK_5 SK_ID_2 item1b item2b item3b item4b ... 

Since the form is tied to a single submit button, I need to somehow set up or modify the above submitListItems function to submit two separate JSON objects to two separate lists, at the same time, and then report on the success or fail status. If either fail, it needs to roll back the changes on the one that did succeed and report back to the user to resubmit.

How can I best go about this? I had started reading into batch processing at this link, but I need to modify the above structure to submit two separate JSON objects to their respective lists.

Additionally, the JSON object I am passing the function works fine for the items going into list1, being a simple:

var jsonObj = { key1: value, key2: value, key3: value, key4: value }; 

    2 Answers 2

    0

    Are you talking about classic SP Forms? If so, you can create a function called

    PreSaveAction(){...}

    This function will be called if the user clicks on submit. You can do some stuff here. If this function returns true, the normal submitting process is initiated (submit and redirect). If it returns false, the submit will not be executed.

    Be aware that additional REST-Operations tend to be executed asynchronously. You would need to return false and handle the whole submitting process manually.

    Be also aware that if you handle the submitting process manually, you could overwrite changes made in the meantime. You would need to check this as well.

    There is the possibility of doing batch requests, but I don't know if it implements transaction management (I would think it doesn't). I wouldn't recommend doing a batch request for two modifications and just chain the operations. You probably will just get one changeset per batch request instead of #operation changesets.

    As for the error you get on submitting: You should check the error message. I think they are often quite explanatory in SP. I would guess your itemtype is wrong. Be aware that spaces are replaced by 'x005f' for example (There are other replacements!). Your GetItemTypeForListName()-function should only work for simple names. You can check the itemtype by doing a get request for the list in question, it should be returned with all the metadata.

    As for a solution to your problem: I tend to just simply chain the operations in simple javascript style: Next operation is always executed in the success-callback of the previous operation. The normal javascript callback hell. Not very efficient, but it might suffice for your needs. List REST-operations tend to be quite fast compared to anything else in SP, so it wouldn't be that noticeable.

    Works, if your operations don't get too complex.

    7
    • Actually I'm having to create a full-page web-part as an aspx page and then effectively hosting the site in SharePoint, as I actually don't know how to use the native SharePoint infrastructure. Everything thus far is custom html / css / javascript with Bootstrap and using AJAX for REST calls. Didn't have time to learn the SharePoint framework or classic SP forms. The direction the code seems to be going is that for one record submitted, I have two separate lists. One list is one record = one item. The second list is one record can have many items, and it gets a unique identifier by reachingCommentedNov 26, 2018 at 14:08
    • * reaching out to the main list using a GET to retrieve the list length and assigning a new sequential number based on the list length to use as my primary key. The issue is that I want both REST calls done asynchronously, with the first list being a normal POST operation, the other being a batch POST, as each entry submitted through this form would have a minimum of 10 items submitted to the second list. I've found pieces of code here and there that I'm attempting to piece together for it, but I don't see many examples of where people have done this in SharePoint.CommentedNov 26, 2018 at 14:14
    • My concern with chaining is that if one of the POST lists fails, I won't be able to roll back changes to the other list or abort the submit to list for the user to re-submit.CommentedNov 26, 2018 at 14:21
    • For the error message, I'm getting this: {"readyState":0,"status":0,"statusText":"error"}CommentedNov 26, 2018 at 14:32
    • I see. I am afraid that there is no transaction management integrated for REST-calls. If you create a batch call and one operation fails, the others within the batch would not be rolled back. So you would need to GET the current state before POSTing any changes to a list. I think there is no way around doing it manually.
      – Anonymous
      CommentedNov 26, 2018 at 15:08
    0

    Here is an implementation based on some research by modifying this and combining it with this (specifically here) and trial and error. I have a JSON object and a JSON array of objects that I need to add to my respective lists. The first object needs to be passed directly in using AJAX REST calls, while the second needs to be processed as a batch using AJAX REST calls:

    // Get List Item Type metadata function GetItemTypeForListName(name) { return "SP.Data." + name.charAt(0).toUpperCase() + name.split(" ").join("").slice(1) + "ListItem"; } function submitListItems(listName, jsonObj, _async) { var jsonMeta = {'__metadata': {'type': GetItemTypeForListName(listName)}}; // Merge the two JSON lists to include SP metadata information jsonObj = $.extend(jsonMeta, jsonObj); // Update form digest for X-Request-Digest UpdateFormDigest(_spPageContextInfo.webServerRelativeUrl, _spFormDigestRefreshInterval); var baseRequest = { url: "", type: "", contentType: "", data: "" }; var request = baseRequest; request.type = 'POST'; request.contentType = 'application/json;odata=verbose'; request.data = JSON.stringify(jsonObj); request.url = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/GetByTitle('" + listName + "')/items"; request.headers = { ACCEPT: 'application/json;odata=verbose', 'X-RequestDigest': $("#__REQUESTDIGEST").val() }; return $.ajax(request); }; /* * @name generateUUID * @description * Generates a GUID-like string, used in OData HTTP batches. * * @returns {string} - A GUID-like string. */ function generateUUID() { var d = new Date().getTime(); var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16); }); return uuid; }; function submitBatchListItems(listName, jsonArr, _async) { // Add SP metadata information to jsonArr array jsonArr = jsonArr.map(x => ({'__metadata': {'type': GetItemTypeForListName(listName)}, ...x})); // Update form digest for X-Request-Digest UpdateFormDigest(_spPageContextInfo.webServerRelativeUrl, _spFormDigestRefreshInterval); // generate a batch boundary var batchGuid = generateUUID(); // creating the body var batchContents = new Array(); var changeSetId = generateUUID(); // get current host var temp = document.createElement('a'); temp.href = _spPageContextInfo.webAbsoluteUrl; var host = temp.hostname; // for each record in array... for (var recordIndex = 0; recordIndex < jsonArr.length; recordIndex++) { var jsonRecord = jsonArr[recordIndex]; // create the request endpoint var endpoint = _spPageContextInfo.webAbsoluteUrl + '/_api/web/lists/getbytitle(' + listName + ')/items'; // create the changeset batchContents.push('--changeset_' + changeSetId); batchContents.push('Content-Type: application/http'); batchContents.push('Content-Transfer-Encoding: binary'); batchContents.push(''); batchContents.push('POST ' + endpoint + ' HTTP/1.1'); batchContents.push('Content-Type: application/json;odata=verbose'); batchContents.push(''); batchContents.push(JSON.stringify(jsonRecord)); batchContents.push(''); } // END changeset to create data batchContents.push('--changeset_' + changeSetId + '--'); // generate the body of the batch var batchBody = batchContents.join('\r\n'); // start with a clean array batchContents = new Array(); // create batch for creating items batchContents.push('--batch_' + batchGuid); batchContents.push('Content-Type: multipart/mixed; boundary="changeset_' + changeSetId + '"'); batchContents.push('Content-Length: ' + batchBody.length); batchContents.push('Content-Transfer-Encoding: binary'); batchContents.push(''); batchContents.push(batchBody); batchContents.push(''); // create request in batch to get all items after all are created endpoint = _spPageContextInfo.webAbsoluteUrl + '/_api/web/lists/getbytitle(' + listName + ')/items?$orderby=Title'; batchContents.push('--batch_' + batchGuid); batchContents.push('Content-Type: application/http'); batchContents.push('Content-Transfer-Encoding: binary'); batchContents.push(''); batchContents.push('GET ' + endpoint + ' HTTP/1.1'); batchContents.push('Accept: application/json;odata=verbose'); batchContents.push(''); batchContents.push('--batch_' + batchGuid + '--'); batchBody = batchContents.join('\r\n'); // create the batch console.debug(batchBody); // create the request endpoint var endpoint = _spPageContextInfo.webAbsoluteUrl + '/_api/$batch'; // batches need a specific header var batchRequestHeader = { 'X-RequestDigest': $("#__REQUESTDIGEST").val(), 'Content-Type': 'multipart/mixed; boundary="batch_' + batchGuid + '"' }; // Create AJAX request and return resultant state var baseRequest = { url: "", type: "", headers: "", data: "" }; var request = baseRequest; request.url = endpoint; request.type = 'POST'; request.headers = batchRequestHeader; request.data = batchBody; return $.ajax(request); }; 

    With using the below to call the above functions as needed:

    function submitForm() { var list1DataInfo = $.Deferred(); var list2DataInfo = $.Deferred(); var dfdList1DataFiles; var dfdList2DataFiles; // Collect data into JSON object var jsonData = packageFormData(); var jsonAmountData = packageFormDataAmounts(); // Submit the data to the list tables dfdList1DataFiles = submitListItems('list1Name', jsonData, true); dfdList2DataFiles = submitBatchListItems('list2Name', jsonAmountData, true); dfdList1DataFiles.done(function(pdoDataResponse) { console.log("dfdList1DataFiles.done"); list1DataInfo.resolve(pdoDataResponse); }); dfdList2DataFiles.done(function(pdoAmountResponse) { console.log("dfdList2DataFiles.done"); list2DataInfo.resolve(pdoAmountResponse); }); dfdList1DataFiles.fail(function(error) { console.log("dfdList1DataFiles.fail"); console.log(JSON.stringify(error)); list1DataInfo.fail(error); }); dfdList2DataFiles.fail(function(lerror) { console.log("dfdList2DataFiles.fail"); console.log(JSON.stringify(lerror)); list2DataInfo.fail(lerror); }); $.when(list1DataInfo, list2DataInfo) .then(function(list1Response, list2Response) { submitRecordSuccess(list1Response, list2Response) }, function(error, lerror) { submitRecordFail(error, lerror); }); }; 

    This have the effect of submitting to the two separate lists without having to chain the AJAX calls.

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.