In "Creating Cross-Device Compatible Mobile Applications that Integrate with SharePoint," the first article in a multipart series on this topic, I explained various approaches that you can take to build mobile applications that integrate with SharePoint line-of-business (LOB) systems. In this article, the next one in the series, I'll demonstrate how to build upon the ASMX APIs described in the first article by introducing search integration and showing you how to perform create, read, update, and delete (CRUD) operations on SharePoint list data using the REST APIs. I will also introduce additional authentication techniques that use the out-of-the-box login page for a forms-based authentication–secured SharePoint web application.

The mobile application in this article series allows users to perform CRUD operations on data in a SharePoint list. Keep in mind that the application takes advantage of the PhoneGap framework (I used PhoneGap 1.7 for the sample app) and works on many different mobile devices. In "Creating Cross-Device Compatible Mobile Applications that Integrate with SharePoint," I showed the application running in the Android and Windows Phone 7 emulators; here I will also show it running in the iOS emulator. One final note before we begin: As you follow along with the examples in this article, make sure you have the sample app from the previous article and modify that sample code as described in the examples here.

Authentication Approaches

In the example application, I'll demonstrate how to use PhoneGap, jQuery, jQuery Mobile, JavaScript, and HTML5 to extend the mobile application from the first article so that the app will perform CRUD operations on SharePoint list data using the REST APIs. I'll demonstrate how to target Windows Phone 7, iPhone, iPad, and Android devices.

When you use the REST APIs to access SharePoint list data, you must authenticate yourself to the SharePoint server before you begin using the REST APIs, just as you do when you use the ASMX web services. There are two approaches you can take to do this. The first approach is to use the same technique I outlined in the first article, where you use the authentication.asmx web service to pass credentials to the server and retrieve a cookie that will be used to authenticate future requests.

The second approach is to attempt to make a REST API call and inspect the result of the API call to see whether the user has already been authenticated. If the user has not been authenticated, you redirect the user to the login page in SharePoint where the user can enter his or her credentials. If the user has already been authenticated, you process the results of the REST API call.

In Figure 1 you can see the new and improved version of the application from the first article in this series.

144415_fig1_mobile_app_main_page-sm
Figure 1: Mobile Application Main Page

Notice the new button labeled Get Items With Redirect. This button takes the approach I just outlined and redirects a user to the SharePoint login page if the user has not already authenticated.

To add the button, add the following code to the onDeviceReady function in the init.js file:

$("#topnavcontent").append("<a href=\"#\" data-role=\"button\" data-icon=\"check\" onclick=\"GetItemsREST(); return false;\">Get Items With Redirect</a>");

Then add the code to display the result in main.html:

<ul data-role="listview" data-inset="true" id="listview" ></ul>

When the Get Items With Redirect button is clicked, the GetItemsREST function is called. Inside this function, shown in Listing 1, I first empty the listview on the page that is used to display the results of the API call. Then I use the jQuery getJSON function to query the REST API to return the items from the Ski Resorts SharePoint list. Notice the RESTGetItemsError callback function is registered with the error condition.

 

Listing 1: Querying List Items with the REST API

                              function GetItemsREST() {                                  $("#listview").empty();                                  $.getJSON("http://server/_vti_bin/ListData.svc/SkiResorts",                              function (data) {                                     	$.each(data.d.results, function (key, result) {                                          var output = "<li>" +                                                          "<a href=\"#\" data-theme=\"b\" data-icon=\"gear\" " +                                                              "onclick=\"ShowDetails('" + result.Id +                                                              "','" + result.Title +                                                              "','" + escape(result.__metadata.etag) +                                                              "'); return false;\">" +                                                              result.Title +                                                              "</a>" +                                                          "</li>";                              $("#listview").append(output);                              $("#listview").trigger("create");                                      });                                  }).error(processRESTGetItemsError);                              }                              

The RESTGetItemsError callback function, shown in the following example, is responsible for determining whether the error occurred because the user is not already authenticated and for redirecting the user to the SharePoint login page.
 

function processRESTGetItemsError(xhr) {
    if (xhr.status == 12150 || xhr.status == 302) {
window.location = “http://server/_forms/default.aspx?ReturnUrl=" + "http://server/SitePages/MobileRedirect.aspx";
    }
}

If the status of the XMLHttpRequest object is 12150 or 302, we can determine that the user was not authenticated. When this is the case, the user is then redirected to the login page on the SharePoint server.

Notice the ReturnUrl QueryString parameter that is passed to the login page. This QueryString parameter instructs SharePoint to navigate to that web page after the user fills out the credentials in the login form and is successfully authenticated. This is a tricky piece of functionality to work with because the ReturnUrl parameter can be used only to redirect users to a page on the SharePoint website. However, we want to redirect users back to the page in our mobile application, which is running inside the mobile application’s web browser.

So, how can you redirect users back to the appropriate page in the mobile device when following this approach? There are a couple of options you can use. The first option is to embed some JavaScript inside the SharePoint web page that you redirect your user to after a successful login. This embedded JavaScript is used to redirect the user again to the page you want them to land on in your mobile application. To accomplish this, I created a new SharePoint page and named it mobileredirect.aspx. Then I created the following JavaScript file to redirect the user back to the page in the mobile application and uploaded the JavaScript file to the SharePoint website.
 

<script language="javascript">
   window.history.go(-2);
</script>


Finally, on the mobileredirect.aspx page, I added a Content Editor Web Part and configured it to load the JavaScript file by entering the URL to the JavaScript file in the Content Link text box in the Content Editor Web Part tool pane. With this approach, after the user is authenticated and redirected to the mobileredirect.aspx page, the user will automatically be returned to the page in the mobile application where he or she initiated the request.

This scenario, as depicted in Figure 2, requires minimal configuration and coding on the SharePoint server.



144415_fig2_page_flow_sharepoint_login-sm
Figure 2: Page Flow When Using the SharePoint Login Page

However because you are using the history in the web browser to navigate backward two pages, this could be considered a fragile approach. Whether you choose to implement this approach in your applications is up to you, and if you carefully design your applications, it is certainly a viable approach. For development purposes, this approach is expedient because it can get you up and running in a matter of minutes.

At this point, when the REST API call is made again, the callback function in the GetItemsREST function processes the list items returned from SharePoint and displays them on the page, as shown in Figure 3.

144415_fig3_sharepoint_list_rest_api-sm

Figure 3: SharePoint List Items Displayed on Page After a Successful REST API Call


As a point of reference, you can see what the data looks like in the Ski Resorts SharePoint list in Figure 4.

144415_fig4_data_ski_resorts_sharepoint_list
Figure 4: Data as It Appears in the Ski Resorts SharePoint List

Clicking the Authenticate button uses the approach described in the first article in this series to call the authentication.asmx web service, then redirects the user to the main application menu, as shown in Figure 5.

144415_fig5_redirect_main_app_menu-sm
Figure 5: Redirecting the User to the Main Application Menu


Walking Through the Application

The first difference you'll notice in the application compared to the version of the app in the previous article is the Search button. Clicking the Search button loads the search page, where you can enter a search term. When the Search button is clicked, the SearchSharePoint function, shown in Listing 2, is called. This function uses jQuery Ajax to make a POST operation to the SharePoint search.asmx web service.

Listing 2: Searching for Items in SharePoint

                              function SearchSharePoint(searchTerm) {                                   var queryPacket = escapeQueryPacket("<QueryPacket xmlns='urn:Microsoft.Search.Query' Revision='1000'>" +                              		"<Query>" +                              			"<Context>" +                              "<QueryText language='en-US' type='STRING'>" + searchTerm + "</QueryText>" +                              			"</Context>" +                              "</Query>" +                              	"</QueryPacket>");                              var searchXML =                               "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"                               	xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"                               	xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +                                           	    "<soap:Body>" +                              			    	"<Query xmlns='urn:Microsoft.Search'>" +                              					"<queryXml>" +                              						queryPacket +                               "</queryXml>" +                              "</Query>" +                              "</soap:Body>" +                                              "</soap:Envelope>";                                  $.ajax({                                      url: "http://server/_vti_bin/search.asmx",                                      type: "POST",                                      dataType: "xml",                                      data: searchXML,                                      complete: processSearchResultSuccess,                                      contentType: "text/xml;charset='utf-8'",                                      error: processSearchResultError                                  });                              }                              function processSearchResultError(xData, status) {                                  var errorMessage = (xData.status + " " + xData.statusText + "<br/>" + xData.responseText);                                  $("#errorContent").empty();                                  $("#errorContent").append("An error has occurred:</br>" + errorMessage + "</br>");                                  $("#errorContent").trigger("create");                              

The SearchSharePoint function calls the escapeQueryPacket function to escape the characters in the queryXml that is submitted, as follows:
 

function escapeQueryPacket(queryPacketXML) {
    return queryPacketXML.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}


After the request to the search.asmx SharePoint web service is completed, the processSearchResultSuccess function, shown in Listing 3, loops through the results and adds them to the jQuery Mobile listview to display them.

Listing 3: Processing Search Results

                              function processSearchResultSuccess(xData, status) {                                  $("#listview").empty();                                  var output = "";                                  $(xData.responseXML).find("QueryResult").each(function () {                                      var queryResult = $(xData.responseXML).find("QueryResult").text();                                      var xmlDoc = $.parseXML(queryResult);                                      var $xml = $(xmlDoc);                                      $xml.find("Document").each(function () {                                          var title = $("Title", $(this)).text();                                          var url = $("Action>LinkUrl", $(this)).text();                                          var description = $("Description", $(this)).text()                                          var output = "<li>" +                                                          "<a href=\"#\" data-theme=\"a\" data-icon=\"gear\" " +                                                              "onclick=\"ShowDetails('" +                                                              url + "','" + description +                                                              "'); return false;\">" +                                                              title +                                                              "</a>" +                                                          "</li>";                                          $("#listview").append(output);                                      });                                  });                                  $("#listview").listview("refresh");                               }                              


You can see the search function in action in Figure 6, which shows the search result for the term "Monarch" as it appears in an iPhone.



144415_fig6_search_result_smartphone-sm
Figure 6: Search Result on Smartphone for "Monarch"

The next difference you will notice on the main menu in Figure 5 is the REST Operations button. Clicking the REST Operations button loads the same exact user interface described in the previous article where I explained how to perform CRUD operations on list items in the Ski Resorts List. The functions that support the REST operations work much like their ASMX counterparts described in the previous article.

The GetItems function, shown in Listing 4, retrieves all the items from the Ski Resorts list.

Listing 4: Querying List Items

                              function GetItems() {                                  $("#listview").empty();                                  $.getJSON("http://server/_vti_bin/ListData.svc/SkiResorts", function (data) {                                      $.each(data.d.results, function (key, result) {                                          var output = "<li>" +                                                          "<a href=\"#\" data-theme=\"b\" data-icon=\"gear\" " +                                                              "onclick=\"ShowDetails('" + result.Id +                                                              "','" + result.Title +                                                              "','" + escape(result.__metadata.etag) +                                                              "'); return false;\">" +                                                              result.Title +                                                              "</a>" +                                                          "</li>";                                          $("#listview").append(output);                                          $("#listview").listview("refresh");                                      });                                  });                                  }                              function processGetItemsResultError(xData, status) {                                  $("#errorContent").empty();                                  $("#errorContent").append("Get Items Result:" + status + "</br>");                                  $("#errorContent").trigger("create");                              }                              


It uses the ListData.svc REST API to query the Ski Resorts list. The getJSON jQuery function is used to make the JSON request to the service. I use the jQuery Ajax functionality to update a list item with the REST API. The ID for the list item to edit is passed to the ListData.svc to identify which item will be updated.

Now let's look at the function beforeSendFunction, shown in Listing 5, which is registered with the Ajax request.

 

Listing 5: Updating a List Item

                              function Update(ID, Title, ETag) {                                  var beforeSendFunction;                                  var listItemData = {};                                  url = "http://server/_vti_bin/listdata.svc/SkiResorts" + "(" + ID + ")";                                  beforeSendFunction = function (xhr) {                                      xhr.setRequestHeader("If-Match", unescape(ETag).replace("~", "/"));                                      xhr.setRequestHeader("X-HTTP-Method", 'MERGE');                                  }                                  listItemData.Title = Title;                                  var data = JSON.stringify(listItemData);                                  $.ajax({                                      type: 'POST',                                      url: url,                                      contentType: 'application/json',                                      processData: false,                                      beforeSend: beforeSendFunction,                                      data: data,                                      success: function () {                                  //Replace RESTMain.html with the page you created to execute the REST queries.                                          window.location = "RESTmain.html";                                      },                                      error: processRESTUpdateError                                  });                              }                              function processRESTUpdateError(xData, status) {                                  var errorMessage = (xData.status + " " + xData.statusText + "<br/>" + xData.responseText);                                  $("#errorContent").empty();                                  $("#errorContent").append("An error has occurred:</br>" + errorMessage + "</br>");                                  $("#errorContent").trigger("create");                              }                              


The ETag variable was set by storing the result.__metadata.etag value returned for each list item by the GetItems method. The ETag value indicates the current version of the item on the server when the list item was retrieved. The REST interface uses etags for concurrency control and detects whether the server version has changed in the interval between when the client retrieved the item and when it updated the item. If the version has changed, the ListData.svc service will reject the update.

Also note that the request header instructs the ListData.svc service to use an HTTP MERGE method to update the item. Using the MERGE verb specifies that the REST service should update only the fields that are included in the request. However, the actual Ajax request is sent to the server as a POST verb to prevent firewall rules from blocking the request because firewalls rules often block HTTP requests that use extended verbs such as MERGE.

To create a list item using the REST API, we again use the jQuery Ajax functionality. The listItemData JSON object, shown in Listing 6, holds the column values that are passed to the ListData.svc to create the new item.

Listing 6: Creating a List Item

                              function Create(title) {                                  var listItemData = {};                                  listItemData.Title = title;                                  var data = JSON.stringify(listItemData);                                                                    $.ajax({                                      type: 'POST',                                      url: httpServerURL + "/_vti_bin/listdata.svc/SkiResorts",                                      contentType: 'application/json',                                      processData: false,                                      data: data,                                      success: function () {                                          window.location = "RESTmain.html";                                      },                                      error: processRESTCreateError                                  });                              }                              function processRESTCreateError(xData, status) {                                  var errorMessage = (xData.status + " " + xData.statusText + "<br/>" + xData.responseText);                                  $("#errorContent").empty();                                  $("#errorContent").append("An error has occurred:</br>" + errorMessage + "</br>");                                  $("#errorContent").trigger("create");                              }                              


In this example, I am creating just the Title column. The contents of the JSON object that represents the new list item are serialized as a string and added to the Ajax request. Figure 7 shows what the Add A New Resort page looks like on the iPhone.



144415_fig7_add_new_resort_page-sm
Figure 7: Add A New Resort Page on iPhone


Deleting an item with the REST API is simple, as shown in Listing 7.


Listing 7: Deleting a List Item

                              function DeleteItem(ID) {                                      url = httpServerURL + "/_vti_bin/listdata.svc/SkiResorts" + "(" + ID + ")";                                                                    $.ajax({                                      type: 'DELETE',                                      url: url,                                      contentType: 'application/json',                                      processData: false,                                      success: function () {                                          window.location = "RESTmain.html";                                      },                                      error: processRESTDeleteError                                  });                              }                              function processRESTDeleteError(xData, status) {                                  var errorMessage = (xData.status + " " + xData.statusText + "<br/>" + xData.responseText);                                  $("#errorContent").empty();                                  $("#errorContent").append("An error has occurred:</br>" + errorMessage + "</br>");                                  $("#errorContent").trigger("create");                              }                              


Again we use the jQuery Ajax functionality to call the ListData.svc service. The ID is passed in the request URL, and the type is set to DELETE.

Another new button you will notice on the main menu is the Excel Services button. Clicking the Excel Services button invokes the Excel Services REST APIs to display a chart from a Microsoft Excel workbook stored on the SharePoint server. The Excel workbook, viewed in a web browser as shown in Figure 8, contains annual snowfall data for Breckenridge Ski Resort and generates a bar graph based on the data.



144415_fig8_view_excel_wkbk_browser-sm
Figure 8: Viewing a Microsoft Excel Workbook via a Web Browser

To display the chart in the mobile application, I simply add an img element to the HTML that comprises the web page, setting the src attribute to use the ExcelRest.aspx web page to locate the Excel workbook and the chart inside it and specifying the format for the return to be an image.

<img src="http://server/_vti_bin/ExcelRest.aspx/Shared%20Documents/SnowFall.xlsx/Model/Charts('Chart%201')?format=image" width="320" height="320"/>

Notice when the workbook is open in the Excel client application, the name of the chart is Chart 1, as you can see in Figure 9. This is the value used in the src attribute for the img element in the mobile application web page's HTML source.

144415_fig9_excel_wkbk_client_app-sm
Figure 9: Microsoft Excel Workbook Opened in the Excel Client App

 

You will also notice that when the workbook is viewed in the Excel client application, the bars in the chart are 3D cylinders. However, when the bars are rendered in the mobile device or web browser, they appear as 2D flat rectangles. This is not a limitation of the mobile device; instead, it is how Excel Services renders charts. Figure 10 shows what the chart looks like when viewed in a Windows Phone 7 mobile device.


144415_fig10_excel_chart_windows_phone_7-sm
Figure 10: Excel Chart Viewed in a Windows Phone 7 Mobile Device

 

This is such a simple pattern that I‘m sure you can imagine how easy it is to create a dashboard-like application with Excel Services as the back end!

Versatile Services

In addition to using the ASMX web services to interact with SharePoint lists, you can also use them to search for data in a SharePoint site and display charts from Excel Services. There are many other ASMX web services that you can also interact with by using many of the same patterns described in this article series.

The REST APIs also offer a full complement of CRUD operations for SharePoint list data. As you have seen, the login page in a SharePoint site may even be used to authenticate users. In my next article, I will demonstrate some seriously cutting-edge technology when I show you how to interact with SharePoint 2013 from mobile devices.