Moving from SPServices to REST, Part 6: Converting UpdateListItems to REST

This entry is part 6 of 6 in the series Moving from SPServices to REST

While the majority of calls using SOAP with SPServices use GetListItems to get items from lists, changing data with UpdateListItems is probably a close second. In this case, we’re altering data based on some user action. In a way, it’s like repainting your car (which I’ve never done). The shape and structure of the car stays the same, but you make it look better with new paint. New data can be like that – after all, the list or library itself doesn’t change.

Image source: http://www.internetbillboards.net/2015/03/27/a-step-by-step-diy-guide-to-give-your-car-a-new-paint-job/
Image source: http://www.internetbillboards.net/2015/03/27/a-step-by-step-diy-guide-to-give-your-car-a-new-paint-job/

Let’s look at a simple example of an update: deleting a list item. This is probably the simplest update because all we have to pass in is the ID of the list item and ask for a delete.

var itemID = 1;
var p = $().SPServices({
  operation: "UpdateListItems",
  listName: "Tasks",
  updates: "<Batch OnError='Continue'> " +
    "<Method ID='1' Cmd='Delete'>" +
    "<Field Name='ID'>" + itemID + "</Field>" +
    "</Method>" +
    "</Batch>"
});
p.done({
  // Manage success or failure here
});

To delete a document in a library, it’s just a little bit harder. We need to pass in the FileRef (where the file lives) in addition to the ID.

var itemID = 1;
var fileRef = "http://mydomain/libName/fileName";
var p = $().SPServices({
  operation: "UpdateListItems",
  listName: "Tasks",
  updates: "<Batch OnError='Continue'> " +
    "<Method ID='1' Cmd='Delete'>" +
    "<Field Name='ID'>" + itemID + "</Field>" +
    "<Field Name='FileRef'>" + fileRef + "</Field>" +
    "</Method>" +
    "</Batch>"
});
p.done({
  // Manage success or failure here
});

In both cases, we pass in what’s called an update, which contains a batch. We can request multiple actions in the batch, but in this case, I’m keeping it very simple and just requesting one action: deleting the item with ID = itemID (and FileRef = fileRef for the library). As you can see, I also need to pass in the Cmd = “Delete” to tell the server what I want it to do. My other options are “New” and “Update”.

If our delete is successful, we simply get a result that tells us the error code was 0x00000000. If there was an issue, we’ll get something else (usually 0x81020016) which may not be all that descriptive of the error.

The call to delete a list item looks like this in REST:

var itemID = 1;
var p = $.ajax({
  url: _spPageContextInfo.webAbsoluteUrl +
    "/_api/web/lists/GetByTitle('Tasks')/items(" + itemID + ")",
  method: "POST",
  headers: {
    "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value,
    "X-HTTP-Method": "DELETE",
    "IF-MATCH": "*",
    success: successFunction,
    error: errorFunction
  }
});

We can check what’s happened one of two ways. You can see above that I’ve set a variable called p to the results of the REST call. I’m also doing something with success and failure within the call. This is redundant, but I’m showing here to demonstrate the two methods (which I probably should have covered in my previous article Part 5: Using Promises with SOAP and REST, as several people have pointed out).

Examining p and acting on it is working with a promise; acting based on the success or failure inside the call is called a callback. You’ll find no end of arguing about which of the two is more “correct”, but it’s really a stylistic choice. Usually if you have lots going on and need to manage it all together, people will agree that working with promises can be easier to follow. There’s nothing wrong with using callbacks as long as you can follow your program logic.

If there’s an issue in the call (regardless which approach you take), you may an error back that looks something like this:

{"error":{"code":"-2130575338, System.ArgumentException","message":{"lang":"en-US","value":"Item does not exist. It may have been deleted by another user."}}}

In this case, I’ve tried to delete an item which doesn’t exist. Note that with REST, unlike with SOAP, no response on an update or delete is good news – you only get a response if something’s gone wrong.

Because I’m changing some data on the server, the request has to be a POST rather than a GET. POSTs are a little harder to set up than GETs because we have to demonstrate to the server that we aren’t bad guys by passing a little more information about who we are and where we are coming from.

Keep in mind that these examples don’t reflect what you would need to do in Apps Add-Ins; that authorization flow has been written about in many other places. These examples show what you would do if you are loading your JavaScript directly in the page, perhaps by including it in a Content Editor Web Part (CEWP) using the Content Link or in a Script Editor Web Part (SEWP).

We pass this extra authorization information in the request header:

"X-RequestDigest": document.getElementById("__REQUESTDIGEST").value

In every SharePoint page in SharePoint 2010 and above, there is a request digest value stored in the page in a hidden element with the id of “__REQUESTDIGEST”. Note that’s two underscores up front, not just one. In the line above I’m using simple JavaScript (no real need for jQuery here) to grab that value.

In both REST examples above I’m doing what I think of as a “selfish delete”. By specifying

"IF-MATCH": "*"

I’m telling the server that I don’t care if anyone else has changed the item or document – I just want it gone. Check out my post on using Etags if you’re interested in how to avoid collisions in requests.

To delete a document in a library, I do basically the same thing:

var itemID = 1;
var p = $.ajax({
  url: _spPageContextInfo.webAbsoluteUrl +
    "/_api/web/lists/GetByTitle('Documents')/items(" + itemID + ")",
  method: "POST",
  headers: {
    "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value,
    "X-HTTP-Method": "DELETE"
    "IF-MATCH": "*",
  }
});
p.then({});
p.fail({});

Now let’s look at something a little harder. We’ll update a field (column) in a list item instead. I’ll keep it simple, but we’ll change the item’s Title.

Here’s how we’d do in in SOAP:

var itemID = 1;
var p = $().SPServices({ 
  operation: "UpdateListItems", 
  listName: "Tasks", 
  updates: "<Batch OnError='Continue'> " +
      "<Method ID='1' Cmd='Update'>" +
      "<Field Name='ID'>" + itemID + "</Field>" + 
      "<Field Name='Title'>This is the new title text</Field>" + 
      "</Method>" + 
      "</Batch>"
});
p.then({});
p.fail({});

As before, I need to pass in the batch, but this time the Cmd is “Update”, meaning that I want to change something about the list item. As you can see, I’m just changing the Title to some arbitrary text.

This SOAP request return us the item or document as it looks after the write. We can inspect that result if we want to update an array or something else we’re maintaining client side.

In REST we need to pass in the data we want the server to use in the update a little differently – no surprise, probably. We still pass the itemID on the url, but the data we want to write goes into a data element. We also need to “stringify” the data because it’s usually represented in our code as a JavaScript object. When we look at JSON data in the developer tools of our favorite browser, it looks like strings, but it’s actually stored as binary content. Since we can only pass text to the server, we need to do this stringify conversion.

var itemID = 1;
var payload = {
    "Title": "This is the new title text"
  };

var p = $.ajax({
  url: _spPageContextInfo.webAbsoluteUrl +
      "/_api/web/lists/GetByTitle('Tasks')/items(" +
        itemID +
      ")",
  method: "POST",
  data: JSON.stringify(payload),
  contentType: "application/json;odata=nometadata",
  headers: {
    "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value,
    "X-HTTP-Method":"MERGE",
    "IF-MATCH": "*"
  }
});

In this example, I’m choosing to pass the data using the contentType with odata=nometadata set. You may see other examples with odata=verbose, and in that case, we must pass a metadata value in the payload, like this:

var payload = {
    "__metadata": {
      "type": "SP.Data.SurveysListItem"
    },
    "Title": "This is the new title text"
  };

Passing the metadata basically adds additional checks to the veracity of our data, and can be a good idea.

I’m still doing a selfish write here (“IF-MATCH”: “*”), not caring whether anyone else has made changes. As with SOAP this means that the last person in wins.

Here’s a table that gives an overview of how REST and SOAP compare when you want to do updates to your SharePoint content.

  SOAP REST
Authorization None needed; our request is made using the current user’s permissions X-RequestDigest

document.getElementById(“__REQUESTDIGEST”).value

Actions Passed in the Cmd element of the update CAML

[New, Update, Delete]

X-HTTP-Method

[PUT, MERGE, PATCH, DELETE]

Data Contained in the updates as strings using CAML notation Contained in the data element and the content must must be “stringified”
Collision Control None – last person wins Using ETags, we can learn of collisions, but we must manage those collisions in our code.

 

References:

This article was also published on IT Unity on 12/7/2015. Visit the post there to read additional comments.
Series Navigation<< Moving from SPServices to Rest, Part 5: Using Promises with SOAP and REST

Similar Posts

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.