2 Added UpdateFormDigest to code
source | link

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. This isn't working fully, as I am getting the response of:

{"readyState":0,"status":0,"statusText":"error"}

from the fail functions, I suspect due to SharePoint believing it to be cross-site posting, but this gives a much clearer idea of what needs to be done and can be used by others:

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

};

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. This isn't working fully, as I am getting the response of:

{"readyState":0,"status":0,"statusText":"error"}

from the fail functions, I suspect due to SharePoint believing it to be cross-site posting, but this gives a much clearer idea of what needs to be done and can be used by others:

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

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

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

};

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

};
1
source | link

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. This isn't working fully, as I am getting the response of:

{"readyState":0,"status":0,"statusText":"error"}

from the fail functions, I suspect due to SharePoint believing it to be cross-site posting, but this gives a much clearer idea of what needs to be done and can be used by others:

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

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

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