CHUVASH.eu

CHunky Universe of Vigourous Astonishing SHarepoint :)

Tag Archives: xhr

XMLHttpRequest the hard way

$.ajax is great, it hides much of the complexity. But sometimes we need to work with “raw” javascript 🙂 So let’s look behind the scenes. The XMLHttpRequest (or just XHR) is used to open a connection to a server without a page reload. Internet Explorer calls it ActiveXObject and it differs in IE versions. Wikipedia article gives a good example how to create one constructor for all browsers:

if (typeof XMLHttpRequest == "undefined")
  XMLHttpRequest = function () {
    try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
      catch (e) {}
    try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
      catch (e) {}
    try { return new ActiveXObject("Microsoft.XMLHTTP"); }
      catch (e) {}
    //Microsoft.XMLHTTP points to Msxml2.XMLHTTP and is redundant
    throw new Error("This browser does not support XMLHttpRequest.");
};

The remainder is more or less the same among the browsers. We open a connection defining the HTTP verb, URI and async mode (true or false):

var xhr = new XMLHttpRequest();
xhr.open("GET", "/_vti_bin/listdata.svc", true);
xhr.onreadystatechange = onStateChange;
xhr.send(null);

Pay attention to onreadystatechange (only lower case letters). If we choose async=false, the UI waits for the response which is not so kind to users, but maybe it is easier to write a program. Well, there is actually no option but to have async=true.

To provide the callback for success and error we can write the responding function onreadystatechange. This function will be called every time the state is changed. There are 5 states:

0 = uninitialized
1 = loading
2 = loaded
3 = interactive
4 = complete

The state 4 “complete” and status 200 (OK) means success.

function onStateChange() {
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      response = xhr.responseText;
    }
  }
}

This onreadystatechange is actually cool stuff. It helps to understand how ajax actually works, and how jQuery “is talking” to browser in order to partially load data. The onreadystatechange callback function opens even new opportunities. What if you want to show a spinner when the data has began to load? Very simple. Let’s just add a couple of code lines to our onStateChange function. When the state is 1 (loading) – show, when 4 (complete) – hide. Suppose we have a spinner object already:

function onStateChange() {
  if (xhr.readyState == 1) {
    spinner.spin();
  }
  if (xhr.readyState == 4) {
    spinner.stop();
    if (xhr.status == 200) {
      response = xhr.responseText;
    }
  }
}

By the way, if you must parse XML from response, check out this blog post by Santosh

Sys.Web.Request (Update 2013-03-05)

In ASP.NET there is a wrapper for this: Sys.Web.Request. I found it in a question on SharePoint stackexchange. Here is a simple example without any error handling, without any parameters, just to show how to invoke the Sys.Web.Request:

function onCompleted(response, eventArgs) {
    var results = response.get_responseData().d.results;
}
var url = "the actual url";
var request = new Sys.Net.WebRequest();
request.set_httpVerb("GET");
request.set_url(url);
request.get_headers()["Accept"] = "application/json";
request.add_completed(onCompleted);
request.invoke();

SP.RequestExecutor

Another way to make an ajax call is to use SP.RequestExecutor which is javascript utility available in SharePoint 15 (maybe not only 15?) under /_layouts/15/sp.requestexecutor.js. I found this thanks to a question on SharePoint StackExchange: Upload a non-text as an attachment using SharePoint 2013 REST API. I mentioned it in my other post: REST API: Add a plain text file as an attachment to a list item:

var request = new SP.RequestExecutor("/");
request.executeAsync({
	url: "MY URL",
	method: "GET"
});

Update list items with listdata.svc

In one of my previous posts I showed how to retrieve data from listdata.svc with jQuery $.getJSON method. Getting data is very simple, listdata.svc provides a powerful restful interface with filtering, expanding and other features. Read the best and short introduction to listdata.svc by Suneet Sharma or a more detailed and technical article about filters, functions and operators by Mike Flasko at Microsoft with short examples here. Now back to listdata.svc in the SharePoint environment. Corey Roth provides a good images and sample results. For REST the “http verbs” are very important: GET, POST, PUT, DELETE..

SharePoint RESTful interface defines these verbs like that:

HTTP verb Data operation
GET Retrieve
POST Create
PUT Update (update all fields and use default values for any undefined fields)
DELETE Delete
MERGE Update (update only the fields that are specified and changed from current version)

If you want to dive into http verbs in the listdata.svc, install cURL and follow the awesome tutorial at JustinLee.sg.

See this slideshare presentation for a quick introduction:

Allright, a higher level of retrieving and updating of data from listdata.svc is presented by Lee Richardson. Lee uses live binding datacontext, which gives you to save changes: dataContext.saveChanges. Another approach is Client object model.

Retrieve data

I’ll give a try $.ajax. We’ll see if it works. The goal is to update an existing item. Let’s take a usual Task list. I have a site in Norwegian (nb-NO), so we’ll see how well it is suited for i18n.

To retrieve all uncompleted tasks for “contoso\administrator”, we can put this into the browser address bar:

http://contoso/_vti_bin/ListData.svc/Oppgaver?$filter=StatusValue ne 'Fullført' and TilordnetTil/Konto eq 'contoso\administrator'

The invoke is case insensitive. This uses two filters.

To invoke this method with ajax, paste it into $.getJSON:

$.getJSON("http://contoso/_vti_bin/ListData.svc/Oppgaver?$filter=StatusValue ne 'Fullført' and TilordnetTil/Konto eq 'contoso%5cAdministrator'",
{}, null
);

Pay attention to backslash in account name. It must be endoded %5c so javascript doesn’t escape it. In this post I won’t provide any gui for this. The result can be inspected in Chrome Dev Tools Network tab.

Create a new item

To create an item, we have to use the POST verb and send a JSON serialized data in a body.

 //create
var url = "/teamsite/_vti_bin/ListData.svc/MyContacts";
var contact = {
	FirstName: "Arnie",
	Title: "Dell",
	WorkCity: "Lund"
};
var body = JSON.stringify(contact);
$.ajax({
	 type: "POST",
	 url: url,
	 contentType: 'application/json',
	 processData: false,
	 data: body,
	 success: function () {
	   alert('Contact Saved.');
	 }
});

Update an item

As described in the Microsoft resource, to update an existing item from javascript, the best verb is POST with MERGE settings. Provide this js function:

beforeSendFunction = function (xhr) {
  xhr.setRequestHeader("If-Match", "*");
  // Using MERGE so that the entire entity doesn't need to be sent over the wire.
  xhr.setRequestHeader("X-HTTP-Method", 'MERGE');
}

And use it in the $.ajax invoke:

 $.ajax({
 	type: "POST",
	...
        beforeSend: beforeSendFunction,
 });

But before we can invoke $.ajax, we must prepare some properties which will be changed. Let’s change Tittel (Title in nb-NO):

var mods = {};
mods.Tittel = "hello verden"
var body = Sys.Serialization.JavaScriptSerializer.serialize(mods);

Now we can post it:

$.ajax({
 	type: "POST",
	contentType: "application/json; charset=utf-8",
	processData: false,
        beforeSend: beforeSendFunction,
 	url: "/_vti_bin/ListData.svc/Oppgaver(3)",
 	data: body,
 	dataType: "json",
	success: function() { console.log("success"); },
	error: function() { console.log("error"); }
 });

And it works!

The beforeSendFunction can be replaced with headers:

//update 
var url = "/teamsite/_vti_bin/ListData.svc/MyContacts(3)";
var mods = {
	FirstName: "Tolle"
};
var body = JSON.stringify(mods);
//update, another example
$.ajax({
 	type: "POST",
	contentType: "application/json; charset=utf-8",
	processData: false,
        headers: {
		"If-Match": "*",
		"X-HTTP-Method": "MERGE"
	},
 	url: url,
 	data: body,
 	dataType: "json",
	success: function() { console.log("success"); },
	error: function() { console.log("error"); }
 });

To update the status is easy too:

var mods = {};
mods.StatusValue = "Fullført"
var body = Sys.Serialization.JavaScriptSerializer.serialize(mods);
$.ajax({
 	type: "POST",
	contentType: "application/json; charset=utf-8",
	processData: false,
        beforeSend: beforeSendFunction,
 	url: "/_vti_bin/ListData.svc/Oppgaver(3)",
 	data: body,
 	dataType: "json",
	success: function() { console.log("success"); },
	error: function() { console.log("error"); }
 });

To create a new item we’ll use POST as well, don’t provide beforeSendFunction:

var mods = {};
mods.Tittel = "david";
mods.TilordnetTilId = 14;
var body = Sys.Serialization.JavaScriptSerializer.serialize(mods);

 $.ajax({
 	type: "POST",
	contentType: "application/json; charset=utf-8",
	processData: false,
 	url: "/_vti_bin/ListData.svc/Oppgaver",
 	data: body,
 	dataType: "json",
	success: function() { console.log("success"); },
	error: function() { console.log("error"); }
 });

Unfortunately I didn’t find any way to set “assigned to” account name, I had to set Account ID (mods.TilordnetTilId = 14).

To delete an item, just use $.ajax and DELETE verb:

$.ajax({
 	type: "DELETE",
	contentType: "application/json; charset=utf-8",
	processData: false,
 	url: "/_vti_bin/ListData.svc/Oppgaver(3)",
 	data: {},
 	dataType: "json",
	success: function() { console.log("success"); },
	error: function() { console.log("error"); }
 });
Localization

What about i18n and localization? It is actually not so generic. One solution could be localizable javascript enums. The fact that the properties are translated can lead to problems. The first time I load the page after deploy, restart or app pool recycling, I get 400 error (bad request). The explanation can be found in the Response:

{
  "error": {
    "code": "",
    "message": {
      "lang": "sv-SE",
      "value": "No property 'TilordnetTilId' exists in type 'Microsoft.SharePoint.Linq.DataServiceEntity' at position 30."
    }
  }
}

It tries to retrieve the Swedish Properties (due my default language in the browser) of the list items. But the list is in Norwegian (nb-NO). In my other browser where I have nb-NO as default language, this problem doesn’t occur. How can we solve this? Sharepoin is too fast here.

And all this stuff: Fullført and other strings that are only valid for one particular language. How to build a solution which can be deployed in different culture environments. The sick is like SPLinq and Localization, the status values are saved as strings in the database, so you can’t use it like enums and so:

An embryo for a possible solution can be using of localization resources like core.resx, where these values are defined:

protected void Page_Load(object sender, EventArgs e)
{
	if (!IsPostBack)
	{
		RegisterLocalizationScript();
	}
}

private void RegisterLocalizationScript()
{
	var completed = SPUtility
		.GetLocalizedString("core", "Tasks_Completed", 
			(uint)CultureInfo.CurrentCulture.LCID);
	var ongoing = SPUtility
		.GetLocalizedString("core", "Tasks_InProgress", 
			(uint)CultureInfo.CurrentCulture.LCID);
	var script = string.Format("completedString = '{0}';ongoingString = '{1}'", 
			completed, ongoing);
	Page.ClientScript.RegisterStartupScript(GetType(), 
		"todo-localized-values", script, true);
}

or:

// this is used to retrieve and update todos with listdata.svc web service
Task = {
	completed: "Fullført", //$Resoures:core.resx, Tasks_Completed
	inProgress: "Pågår" //$Resoures:core.resx, Tasks_InProgress
}
Datetime

To retrieve and order it after creation date add $orderby:

http://contoso/_vti_bin/ListData.svc/Oppgaver?$filter=StatusValue ne 'Fullført' and TilordnetTil/Konto eq 'contoso\administrator'$orderby=Opprettet desc

To filter datetime follow this link:

http://contoso/_vti_bin/ListData.svc/Oppgaver?$filter=Endret+ge+datetime'2011-11-23T00:00:00'

To get all items changed the last week:

var date = new Date();
date.setDate(date.getDate()-7);
var lastWeekISO = date.toISOString();
$.getJSON("http://contoso/_vti_bin/ListData.svc/Oppgaver?$filter=Endret ge datetime'" + lastWeekISO + "'",
{}, null
);

To update/add a date we have to pass an ISO string.

var today =  new Date();
task.Forfallsdato = today.toISOString();
Misc

Only after I wrote this I found a very nice intro to REST by Jan Tielen.

The alternative is to use SPServices or maybe SPRest or SPELL by Christophe by Path To Sharepoint.

The alternative way to retrieve list items and update them is to use Client Object Model (CSOM). One of the advantages of Client Object Model is that you don’t need to care about the localization of column names and status values. You can rely on static names like you do in the Server Object Model when you use CAML.

This image shows differences for retrieving 10 list items using lisdata.svc vs csom:

Вула Чăвашла

VulaCV - Чăвашла вулаттаракан сайт

Discovering SharePoint

And going crazy doing it

Bram de Jager - Coder, Speaker, Author

Office 365, SharePoint and Azure

SharePoint Dragons

Nikander & Margriet on SharePoint

Paul J. Swider - RealActivity

RealActivity is a specialized Microsoft healthcare services and solution advisory firm.

Cameron Dwyer

Office 365, SharePoint, Azure, OnePlace Solutions & Life's Other Little Wonders

paul.tavares

Me and My doings!

Share SharePoint Points !

By Mohit Vashishtha

Jimmy Janlén "Den Scrummande Konsulten"

Erfarenheter, synpunkter och raljerande om Scrum från Jimmy Janlén

Aryan Nava

DevOps, Cloud and Blockchain Consultant

SPJoel

SharePoint for everyone

SharePointRyan

Ryan Dennis is a SharePoint Solution Architect with a passion for SharePoint and PowerShell

SharePoint 2020

The Vision for a Future of Clarity

Aharoni in Unicode

Treacle tarts for great justice

... And All That JS

JavaScript, Web Apps and SharePoint

blksthl

Mostly what I know about SharePoint - CommunicoCuspis