Using the REST Interface from JavaScript

In the Client RI, the Ajax REST interface demonstrates how to use the REST interface from an Ajax client. The interface provides a part search facility, as shown by the following diagram. The user can view the suppliers associated with each part and edit the inventory locations for each part in modal dialog boxes, by clicking the links in each row.

The AJAX REST interface

The JavaScript files that provide this functionality are contained within the Scripts node of the Client.SharePoint project. The project includes three JavaScript files:

  • clientCommon.js. This file contains the functions that are used by both the Ajax REST interface and the Ajax CSOM interface. These functions are primarily used for managing user interface elements.
  • CsomScripts.js. This file contains data access functions that use the client-side object model (CSOM) to implement the scenario. These functions are used by the Ajax CSOM interface.
  • RestScripts.js. This file contains data access functions that use the SharePoint REST interface to implement the scenario. These functions are used by the Ajax REST interface.
Ff798303.note(en-us,PandP.10).gifNote:
The Internet Explorer Developer Toolbar is a useful tool for debugging JavaScript on a Web page. To show the developer toolbar in Internet Explorer 8, press F12.

The Ajax REST interface is provided by the JavascriptWithREST.aspx page. The page itself is a straightforward Web Part page that loads the JavaScript files. Within the page, the Ajax REST interface is defined by the following HTML.

<table>
  <tr>
    <td>
      <input id="skuTextBox" type="text" />
    </td>
    <td style="text-align:left">
      <input id="Button1" type="button" value="Find Parts" />
    </td>
  </tr>
  <tr>
    <td colspan="2">
      <div id="ContentDiv">
      </div>
    </td>
  </tr>
  <tr>
    <td colspan="2">
      <div id="divSuppliers">
        <div id="divSupplierResults">
        </div>
      </div>
    </td>
  </tr>
  <tr>
    <td colspan="2">
      <div id="divLocations">
        <div id="divPartLocations">
        </div>
        <div id="divLocationAdd" style="display:none">
          <input id="hidLocationId" type="hidden" />
          <input id="hidPartId" type="hidden" />
          <table>
            <tr>
              <td>Bin #</td>
              <td><input id="binText" type="text"/></td>
            </tr>
            <tr>
              <td>Quantity</td>
              <td><input id="quantityText" type="text" /></td>
            </tr>
            <tr>
              <td>
                <input id="buttonSave" type="button" value="Save" 
                       onclick="savePartLocation();" />
              </td>
            </tr>
          </table>
        </div>
        <input id="buttonNew" type="button" value="New Location" 
               onclick="showLocation('0','0');" style="display:none" />
      </div>
    </td>
  </tr>
</table>

As you can see from the code, there are many named div elements with no content. This is a common pattern in Ajax-style applications, as the div tags act as placeholders for data that will be retrieved asynchronously from the server and inserted into the page by client-side JavaScript logic.

Ff798303.note(en-us,PandP.10).gifNote:
SharePoint includes a ScriptLink control that you can use to register JavaScript files on an ASPX page. This control offers a number of advantages, such as on-demand loading of the required files. However, the ScriptLink control is not available in the sandbox environment, and as such it is not used in this implementation.

Retrieving Data with Ajax and REST

To gain an understanding of how the application works, consider what happens when the user searches for a part by SKU. The search is triggered when the user clicks the Find Parts button, defined near the top of the preceding code example:

<input id="Button1" type="button" value="Find Parts" />

As you can see, the button isn't declaratively wired to a JavaScript function. This implementation uses a technique known as Unobtrusive JavaScript, which aims to separate markup from script. HTML controls are wired to JavaScript event handlers when the JavaScript file loads. The following function, which is taken from the RestScripts.js file, defines an event handler function named OnButtonClick and associates it with the client-side click event of the Find Parts button.

$(function AssociateButtonClickWithJSONCall() {
  $('#Button1').click(function OnButtonClick(){
    $('#ContentDiv').html("");
    $('#divSupplierResults').html("");
    var sku = $('#skuTextBox').val();
    $.getJSON("/sites/sharepointlist/_vti_bin/listdata.svc/Parts()
               ?$filter=startswith(SKU,'" + sku + "')
               &$select=Title,SKU,Id,Description",
               {},
               function ClearDivsAndMerge(data, status) {
                 var parts = data.d.results;
                 mergePartsWithInventoryLocations(sku, parts);
               }
             );
  });
});

The dollar symbol ($) is shorthand for the global jQuery object and can be used to execute functions, retrieve objects or collections, and perform actions on retrieved objects or collections. In this example, the dollar symbol is used in various key ways:

  • If you enclose a JavaScript function within parentheses preceded by a dollar symbol, jQuery will execute the function when the JavaScript file is loaded. For example, the AssociateButtonClickWithJSONCall function is executed when the browser loads the RestScripts.js file.
  • The jQuery object is used to retrieve named elements from the Web page Document Object Model (DOM). For example, $('#ContentDiv') retrieves the div element with an ID of ContentDiv. This is functionally similar to document.getElementById('ContentDiv') in classic JavaScript.

When the JavaScript file is loaded, jQuery immediately executes the AssociateButtonClickWithJSONCall function. This uses the jQuery click function to wire the OnButtonClick handler to the client-side click event of the Find Parts button (Button1). When the button is clicked, the OnButtonClick method performs the following actions:

  • Clears the ContentDiv element.
  • Clears the divSupplierResults element.
  • Retrieves the search text from the skuTextBox element.
  • Calls the jQuery getJSON method, which uses an HTTP GET request to retrieve JSON-encoded data from the server. The method takes a URL (which in this case corresponds to a REST query), a data object (which in this case is empty), and a callback function that is executed asynchronously if the request succeeds.
Ff798303.note(en-us,PandP.10).gifNote:
You don't have to name the callback function you supply to the getJSON method. However, naming the function improves the debugging experience. The function name will show up in the stack trace during debugging, and the debugging stack can become confused when anonymous functions are used. Tools that minify the JavaScript code for production will typically strip out function names in these scenarios in order to reduce the file size.

The callback function, ClearDivsAndMerge, first retrieves the query results from the data returned by the REST service. It then passes these results, together with the original part SKU search text, to the mergePartsWithInventoryLocations function.

function mergePartsWithInventoryLocations(sku, parts) {
  $.getJSON(
    "/sites/sharepointlist/_vti_bin/listdata.svc/InventoryLocations()
    ?$filter=startswith(Part/SKU,'" + sku + "')
    &$orderby=Part/SKU
    &$expand=Part
    &$select=Id,BinNumber,Quantity,Part/Title,Part/SKU,Part/Id",
    {},
    function mergePartsAndInventory(data) {
      var inventoryLocations = data.d.results;
      var bindingViewsModels = new Array();
      var inventoryPartResults = new Array();
      var noInventoryPartResults = new Array();
      $.each(inventoryLocations, 
             function bindViewModel(index, inventoryLocation) {
               var bindingViewModel =
               {
                 Id: inventoryLocation.Part.Id,
                 SKU: inventoryLocation.Part.SKU,
                 Title: inventoryLocation.Part.Title,
                 InventoryLocationId: inventoryLocation.Id,
                 LocationBin: inventoryLocation.BinNumber,
                 InventoryQuantity: inventoryLocation.Quantity
               };

               bindingViewsModels.push(bindingViewModel);
               inventoryPartResults.push(inventoryLocation.Part.Id);
             });

    //Determine parts with no inventory location
    $.each(parts, function addIfNoInventory(index, part) {
      if (arrayContainsValue(inventoryPartResults, part.Id) != true) {
        noInventoryPartResults.push(part);
        };
      });

    $.each(noInventoryPartResults, 
           function bindNoInventory(index, partWithNoInventoryLocation) {
             var bindingViewModel =
             {
               Id: partWithNoInventoryLocation.Id,
               SKU: partWithNoInventoryLocation.SKU,
               Title: partWithNoInventoryLocation.Title,
               LocationBin: "unassigned",
               InventoryQuantity: ""
             };

             bindingViewsModels.push(bindingViewModel);
           });

    buildTable(bindingViewsModels);
  });
}

The mergePartsWithInventoryLocations function first submits a new REST query to retrieve all the inventory location instances that reference a part with the specified SKU. The function then performs the logical equivalent of a left outer join between parts and inventory locations. Parts and inventory locations are merged into a collection of bindingViewModel objects, which are essentially view projections that include selected fields from both entities. Parts with no associated inventory locations are added to the collection with a LocationBin value of unassigned and an empty InventoryQuantity value. Finally, the function calls the buildTable function, passing in the bindingViewModels collection as an argument.

The buildTable function formats the collection of view models into an HTML table, and then inserts the table into the ContentDiv element on the web page.

function buildTable(viewModels) {
  returnTable = 
   '<table style=\"border: solid 1px black\">
      <tr style=\"font-weight:bold;font-style:underline\">
        <td>ID</td>
        <td>Part Name</td>
        <td>Part SKU</td>
        <td>Bin #</td>
        <td>Quantity</td>
        <td>Inventory</td>
        <td>Suppliers</td>
      </tr>';
  
  for (var i = 0; i < viewModels.length; i++) {
    var item = viewModels[i];
    buildRow(item);
  }

  returnTable = returnTable + '</table>';
  $('#ContentDiv').html(returnTable);
}

The buildRow helper function converts each view model instance into a table row. As you can see, the Inventory and Suppliers fields are rendered as hyperlinks that call JavaScript functions when clicked.

function buildRow(item) {
  var sku = item["SKU"];
  var partTitle = item["Title"];
  var partId = item["Id"];
  var bin = item["LocationBin"];
  var quantity = item["InventoryQuantity"];
  //id needs to be 0 if it doesn't exist
  var id = '0';
  if (item["InventoryLocationId"] !== undefined) {
    id = item["InventoryLocationId"]
  }

  returnTable = returnTable + 
    '<tr><td>' + id + '</td><td>' + 
     partTitle + '</td><td>' + 
     sku + '</td><td>' + 
     bin + '</td><td style=\"text-align:center\">' + 
     quantity + '</td><td>
     <a href=\"javascript:showLocation(\'' + id + '\',\'' + 
     partId + '\');\">|&nbsp;Edit Inventory&nbsp;|</a></td><td>
     <a href=\"javascript:showSuppliers(\'' + 
     partId + '\');\">&nbsp;Suppliers&nbsp;|</a></td></tr>';
}

Updating Data with Ajax and REST

The Ajax REST interface in the Client RI allows users to update inventory locations and quantities. Making updates from JavaScript is a slightly more complex process than making updates using the Silverlight object model. When the user clicks an Edit Inventory link in the UI, a modal dialog is launched that allows users to edit the bin number and the quantity for the selected part.

The Inventory Locations dialog

Ff798303.29a2fadc-ddc6-4ff2-a36a-441691b5fb2f(en-us,PandP.10).png

When the user clicks Save, the savePartLocation method is called.

var savePartLocation = function () {

  var locationId = $('#hidLocationId').val();
  var url = '/sites/sharepointlist/_vti_bin/listdata.svc/InventoryLocations';
  var beforeSendFunction;
  var inventoryLocationModifications = {};

  if (locationId == '0') {
    //Insert a new Inventory Location
    inventoryLocationModifications.PartId = $('#hidPartId').val();
    beforeSendFunction = function () { };
  }
  else {
    //Update Existing Inventory Location
    url = url + "(" + locationId + ")";
    beforeSendFunction = function (xhr) {
      xhr.setRequestHeader("If-Match", inventoryLocation.__metadata.etag);
      //Using an HTTP MERGE so that the entire entity doesn't need to be sent to 
      //the server. 
      xhr.setRequestHeader("X-HTTP-Method", 'MERGE');
    }
  }

  inventoryLocationModifications.BinNumber = $('#binText').val();
  inventoryLocationModifications.Quantity = $('#quantityText').val();

  var body = 
 Sys.Serialization.JavaScriptSerializer.serialize(inventoryLocationModifications);

  $.ajax({
           type: 'POST',
           url: url,
           contentType: 'application/json',
           processData: false,
           beforeSend: beforeSendFunction,
           data: body,
           success: function () {
             alert('Inventory Location Saved.');
           }
         });

  hideLocationDialogue();
}

There are various points of interest in the update operation. First, note that we add an If-Match header that specifies an etag value to the request:

xhr.setRequestHeader("If-Match", inventoryLocation.__metadata.etag);

  • In this case, inventoryLocation is a local variable in the RestScripts.js file. The variable is assigned when the inventory location was originally retrieved from the server, so the __metadata.etag value indicates the current version of the item on the server when the item was retrieved. The REST interface uses etags for concurrency control – if it detects that the server version has changed between the client retrieving the item and the client updating the item, the service will reject the update.
  • Second, note that we add a header that instructs the service to use an HTTP MERGE method to update the item.
xhr.setRequestHeader("X-HTTP-Method", 'MERGE');

  • The use of the MERGE verb indicates that the REST service should only update the fields that are specified in the request. However, this is sent to the server as a POST verb, as firewall rules often block HTTP requests that use extended verbs such as MERGE.

CSOM Cross-Site Collection Limitation

When you use the client-side object model from JavaScript, you can only access sites and objects within the site collection from which the page originated. Attempting to access a different site collection causes an Invalid Form Digest Error, which will typically result in a dialog box warning as shown in the following image.

Invalid Form Digest Error

Ff798303.8cfcf6c1-bda4-41c2-8f4e-5e36a782e473(en-us,PandP.10).png

This limitation may be addressed in future releases of SharePoint 2010.

Show: