facebook google plus twitter
Webucator's Free Ajax Tutorial

Lesson: Ajax Basics

Welcome to our free Ajax tutorial. This tutorial is based on Webucator's Ajax Training course.

As a way of review, the term AJAX is a pseudo-acronym for "Asynchronous JavaScript And XML," but is now used much more broadly to cover all methods of communicating with a server using JavaScript. Again, Ajax is not always asynchronous and does not always involve XML.

Lesson Goals

  • Learn how Ajax Works.
  • Learn how to create cross-browser Ajax.
  • Learn how to send data using Head, Get and Post methods.

The XMLHttpRequest Object

Later in this course we will look at how using JavaScript libraries like jQuery can make working with Ajax quicker and easier. To start, though, it makes sense to review the underlying code to see how the actual Ajax calls and response-handling work. Your work here will make using jQuery (or other libraries) easier - and you'll understand what the jQuery convenience functions are actually doing.

The mechanism for sending data to and retrieving data from the server with Ajax is the XMLHttpRequest object. Until HTML5, the XMLHttpRequest Object wasn't officially part of any common specification; however, all the major browsers have supported it for some time. Later in the course we will use jQuery, a JavaScript library, to create Ajax functionality.

The HTML5 method is straightforward. It uses a simple XMLHttpRequest() constructor to create the object.

Code Sample:

AjaxBasics/Demos/CreateXMLHttpRequest.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="lib.js"></script>
<script type="text/javascript">
	function start() {
		if (window.XMLHttpRequest) {
			var xmlhttp = new XMLHttpRequest();
			document.getElementById("Content").innerHTML =
				"<h1>Using XMLHttpRequest Object</h1>";
		} else {
			var xmlhttp = false;
			document.getElementById("Content").innerHTML =
				"<h1>XMLHttp cannot be created!</h1>";
		}
	}

	observeEvent(window, "load", function() {
		var btn = document.getElementById("btnStart");
		observeEvent(btn, "click", start);
	});
</script>
<title>XMLHttpRequest - HTML5 Method</title>
</head>
<body>
	<button id="btnStart">Start</button>
	<div id="Content"></div>
</body>
</html>

This code attempts to create an XMLHttpRequest object using the XMLHttpRequest() constructor. If it succeeds, it writes out "Using XMLHttpRequest Object" to the body of the page. If it fails, it writes out "XMLHttp cannot be created!"

Using an XMLHttpRequest Object

So, now that we have an XMLHttpRequest object created, what do we do with it? We use it to make HTTP requests. To do so, we initialize the object with the open() method, which takes three arguments.

XMLHttpRequest open() Method Arguments
Argument Description
Request Type String. Usually POST, GET, or HEAD
URL String. The URL receiving the request.
Asynchronous Boolean. Whether the request should be made asynchronously (true) or synchronously (false).

A typical open() method call is shown below.

xmlhttp.open("GET","Demo.xml",true);

Request Types

Although the HTTP specification identifies several methods of HTTP requests , the most commonly supported (and used) methods are GET, POST and HEAD.

HEAD

The HEAD method is the least commonly used of the three; however, for simple requests, it can be all you need. It simply returns the meta-information contained in the HTTP headers. The call would look like this:

xmlhttp.open("HEAD","Demo",true);

And the response might look like this:

Date: Wed, 11 May 2011 15:46:30 GMT 
X-Powered-By: ASP.NET 
Content-Length: 63 
Last-Modified: Tue, 10 May 2011 19:12:27 GMT 
Server: Microsoft-IIS/7.5 
ETag: "712b13346fcc1:0" 
Content-Type: text/xml 
Accept-Ranges: bytes

The XMLHttpRequest request is sent as follows:

xmlhttp.send(null);

We'll explain why null is passed in just a moment.

GET

The GET method is used to send information to the server as part of the URL. The server returns the same header information that the HEAD method returns, but it also returns the body of the message (i.e, the content of the page). Any name-value pairs to be processed by the receiving page should be passed along the querystring. The call would look like this:

xmlhttp.open("GET","Demo?FirstName=Nat&LastName=Dunn",true);

The response would be the same as the response shown for the HEAD method followed by the message body, which would typically be simple text, JSON, HTML or XML.

Again, the XMLHttpRequest request is sent as follows:

xmlhttp.send(null);

POST

The POST method is used to send information as an enclosed entity. The call would look like this:

xmlhttp.open("POST","Demo",true);

The response header is somewhat different in that it specifies that the returned content is not cacheable. Like with GET, the message body would typically be plain text, HTML or XML.

The XMLHttpRequest request is sent as follows:

xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
xmlhttp.send("FirstName=Nat&LastName=Dunn");

As you can see, with POST, we first need to set the content type to "application/x-www-form-urlencoded;charset=UTF-8". This tells the server to expect form data. In the send() method, we include name-value pairs. These name-value pairs are available to the receiving page for processing.

Data cannot be sent in this manner with the HEAD and GET methods, which is why null was passed in the previous examples.

Asynchronous vs. Synchronous Requests

The asynchronous argument should almost always be set to true. After all, that's the "A" in Ajax. Synchronous calls force the browser to wait for a response from the server before continuing. This leaves the user unable to interact with the browser until the response is complete. Asynchronous requests allow the browser to continue to process code while waiting for a response.

Handling the Response

When using asynchronous calls, we cannot be sure when the response will come, so we must write code that waits for the response and handles it when it arrives. We do this with a callback function. Callback functions are functions that are triggered by some event. In our case, the event we are looking for is a change in the state of the xmlhttp response.

The xmlhttp object's readyState property holds the current state of the response. There are five possible states (0-4), which are described below. Browsers do not necessarily inform you of all states; states 0 and 3 in particular may not appear when you run the demo file.

Values of the readyState Property
State Description
0 uninitialized
1 loading
2 loaded
3 interactive
4 complete

Each change in the readyState is captured by the xmlhttp object's onreadystatechange event handler. We can assign a callback function to this property like this:

xmlhttp.onreadystatechange = function() {
	//Do something here
}

This use of an anonymous or unnamed function may be new to you. In JavaScript, functions are first-class objects and can be assigned to variables or properties of other objects. We could also create a named function and assign that function to xmlhttp.onreadystatechange like this:

xmlhttp.onreadystatechange = handler;

The following sample file illustrates how the readystatechange event is handled.

Code Sample:

AjaxBasics/Demos/ReadyStateChange.html
---- C O D E   O M I T T E D ----

<script type="text/javascript">
	function start() {
		var xmlhttp = new XMLHttpRequest();
		var contentDiv = document.getElementById("Content");

		xmlhttp.open("HEAD", "Demo.xml", true);
		xmlhttp.onreadystatechange = function() {
			contentDiv.innerHTML +=
				"Ready State: " + xmlhttp.readyState + "<br>";
		}
		xmlhttp.send(null);
	}
---- C O D E   O M I T T E D ----

The output will look something like this. The actual ready states returned will depend on your setup.Output

In practice, before doing anything with the xmlhttp response data, we want to make sure the readyState is complete (4), so we put a condition inside our function to check for this:

xmlhttp.onreadystatechange=function() {
	if (xmlhttp.readyState==4) {
		//Do something here
	}
}

Now we're ready to do something with the data returned. Before looking at an example, let's take a look at the properties and methods of the xmlhttp object, so we know what's available to us.

XMLHttpRequest Object Properties
Property Description
onreadystatechange Specifies the callback function to be triggered when the ready state changes.
readyState Holds the state of the response.
responseText Holds the message body as a string.
responseXML Holds the message body as an XML object.
status Holds the status code returned from the server (e.g, 200 for success, 404 for page not found, etc.).
statusText Holds the status text returned from the server.
XMLHttpRequest Object Methods
Method Description
abort() Aborts the xmlhttp request.
getAllResponseHeaders() Retrieves the values of all the HTTP headers as a string.
getResponseHeader(header) Retrieves the value of the specified HTTP header as a string.
open(Method,URL,Async) Initializes the XMLHttpRequest object.
send(postData) Sends the HTTP request to the server.
setRequestHeader(header,value) Specifies the name and value of an HTTP header.

A common application is to check the status property to make sure that the request was successful (200) and then to output the message body to a div on the HTML page. The following sample file demonstrates this. To run the demo, first start the Node.js server:

  1. Open the command line (on a PC) or terminal (on a Mac), and navigate to the directory AjaxBasics/Demos/.
  2. Type npm install.
  3. Type npm start to start the Node.js server.

With the server started, you can then browse to http://localhost:8080/UsingXMLHttpRequest-Get.html to view the page.

Code Sample:

AjaxBasics/Demos/UsingXMLHttpRequest-Get.html
---- C O D E   O M I T T E D ----

	function start() {
		var xmlhttp = new XMLHttpRequest();
		var contentDiv = document.getElementById("Content");

		xmlhttp.open("GET", "Demo?FirstName=Nat&LastName=Dunn", true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				contentDiv.innerHTML = xmlhttp.responseText;
			}
		}
		xmlhttp.send(null);
	}
---- C O D E   O M I T T E D ----

This page simply "copies" the response text (xmlhttp.responseText) and "pastes" it into the "Content" div on the page.

Ajax Using the POST Method

Duration: 10 to 15 minutes.

In this exercise, you will modify the code from the last demonstration to send data using the POST method. You will need to use the setRequestHeader() method to alert the server to expect form data.

  1. If needed, stop (by pressing ctrl-c from the command line) any running instance of the Node.js.
  2. Navigate to the directory AjaxBasics/Exercises/ in command line.
  3. Type npm install from the command line, to install the needed Node.js modules.
  4. Type npm start to start the Node.js server.
  5. Open AjaxBasics/Exercises/UsingXMLHttpRequest-Post.html in your editor.
  6. Modify the code to make the request using the POST method shown earlier.
  7. Test your solution in a browser by visiting http://localhost:8080/UsingXMLHttpRequest-Post.html

Code Sample:

AjaxBasics/Exercises/UsingXMLHttpRequest-Post.html
---- C O D E   O M I T T E D ----

<script type="text/javascript">
	function start() {
		var xmlhttp = new XMLHttpRequest();
		var contentDiv = document.getElementById("Content");

		xmlhttp.open("GET", "Demo?FirstName=Nat&LastName=Dunn", true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				contentDiv.innerHTML = xmlhttp.responseText;
			}
		}
		xmlhttp.send(null);
	}

	observeEvent(window, "load", function() {
		var btn = document.getElementById("btnStart");
		observeEvent(btn, "click", start);
	});
</script>
---- C O D E   O M I T T E D ----

Challenge

Write out all the response headers to the end of the same "Content" div.

Solution:

AjaxBasics/Solutions/UsingXMLHttpRequest-Post.html
---- C O D E   O M I T T E D ----

	function start() {
		var xmlhttp = new XMLHttpRequest();
		var contentDiv = document.getElementById("Content");

		xmlhttp.open("POST", "Demo", true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				contentDiv.innerHTML = xmlhttp.responseText;
			}
		}
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("FirstName=Nat&LastName=Dunn");</strong>
	}
---- C O D E   O M I T T E D ----

We specify "POST" as the method for our Ajax call in xmlhttp.open and, in xmlhttp.setRequestHeader specify the MIME type (application/x-www-form-urlencoded) and character encoding (charset=UTF-8) of the request we send to the /Demo response route.

We pass along two POST variables, FirstName and LastName, with values Nat and Dunn, respectively.

Challenge Solution:

AjaxBasics/Solutions/UsingXMLHttpRequest-Post-Challenge.html
---- C O D E   O M I T T E D ----

	function start() {
		var xmlhttp = new XMLHttpRequest();
		var contentDiv = document.getElementById("Content");

		xmlhttp.open("POST", "Demo", true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				contentDiv.innerHTML = xmlhttp.responseText;
				var headers = xmlhttp.getAllResponseHeaders();
				contentDiv.innerHTML += headers.replace(/\n/g, "<br>");
			}
		}
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("FirstName=Nat&LastName=Dunn");
	}
---- C O D E   O M I T T E D ----

We use innerHTML to set the contents of #contentDiv, retrieving the response headers with xmlhttp.getAllResponseHeaders() and displaying with newlines converted to HTML <br /> tags.

The Callback Function

In the demos thus far, we have used anonymous callback functions to hanndle the Ajax response, like so:

xmlhttp.onreadystatechange=function() {
 //handle response
}

As these anonymous functions are nested within the original function, they have access to the variable set in the original function. That's why we can reuse the xmlhttp and contentDiv variables within the callback function:

function start() {
	var xmlhttp = new XMLHttpRequest();
	var contentDiv = document.getElementById("Content");

	xmlhttp.open("POST", "Demo", true);
	xmlhttp.onreadystatechange=function() {
		if (xmlhttp.readyState==4 && xmlhttp.status==200) {
			contentDiv.innerHTML=xmlhttp.responseText;
		}
	}
	xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
	xmlhttp.send("FirstName=Nat&LastName=Dunn");
}

But callback functions don't have to be anonymous or nested. Another way to write this is shown below:

               var xmlhttp = new XMLHttpRequest();
var contentDiv = document.getElementById("Content");

function start() {
	xmlhttp.open("POST", "Demo", true);
	xmlhttp.onreadystatechange=myCallBack;
	xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
	xmlhttp.send("FirstName=Nat&LastName=Dunn");
}

function myCallBack() {
	if (xmlhttp.readyState==4 && xmlhttp.status==200) {
		contentDiv.innerHTML=xmlhttp.responseText;
	}
}

The problem with this method is that it uses global variables. This can be problematic as the variables can easily be unintentionally and unexpectedly overwritten. One way around this is to use an anonymous callback function to make the call to the now-pseudo-callback function and pass the necessary variables into it, like this:

function start() {
	var xmlhttp = new XMLHttpRequest();
	var contentDiv = document.getElementById("Content");
	xmlhttp.open("POST", "Demo", true);
	xmlhttp.onreadystatechange=function() {
		myCallBack(xmlhttp, contentDiv);
	};
	xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
	xmlhttp.send("FirstName=Nat&LastName=Dunn");
}

function myCallBack(xmlhttp, contentDiv) {
	if (xmlhttp.readyState==4 && xmlhttp.status==200) {
		contentDiv.innerHTML=xmlhttp.responseText;
	}
}

Another way to handle it is to nest a named callback function within the original function. As JavaScript functions are objects, they can have their own methods, so you can do the following:

function start() {
	var xmlhttp = new XMLHttpRequest();
	var contentDiv = document.getElementById("Content");
	xmlhttp.open("POST", "Demo", true);
	xmlhttp.onreadystatechange=myCallBack;
	xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
	xmlhttp.send("FirstName=Nat&LastName=Dunn");
	
	function myCallBack() {
		if (xmlhttp.readyState==4 && xmlhttp.status==200) {
			contentDiv.innerHTML=xmlhttp.responseText;
		}
	}
}

Because the callback function is nested within the start() function, it has access to the xmlhttp and contentDiv variables. The only limitation of this method is that the myCallBack() function is not callable from outside the start() function. Depending on what you're trying to do, that may or may not be an issue.

Displaying and Updating Records

Duration: 30 to 40 minutes.

In this exercise, you will create a mini-application for displaying and updating employee records. The server-side files are already created and much of the JavaScript is written for you. You will have to finish the Ajax portions of the code.

  1. From the command line, navigate to the directory AjaxBasics/Exercises/.
  2. Type npm start to start the Node.js server.
  3. Note that the code already created for you makes an Ajax call to /EmployeeList. Generated by Node.js, this call returns a list of the employees, as shown below:
    <ul>
    <li id="emp1">Nancy Davolio</li>
    
    <li id="emp2">Andrew Fuller</li>
    <li id="emp3">Janet Leverling</li>
    <li id="emp4">Margaret Peacock</li>
    <li id="emp5">Steven Buchanan</li>
    <li id="emp6">Michael Suyama</li>
    <li id="emp7">Robert King</li>
    <li id="emp8">Laura Callahan</li>
    <li id="emp9">Anne Dodsworth</li>
    </ul>
    Notice the ids of the list items are the employee id from the database prefixed with "emp".
  4. Open AjaxBasics/Exercises/EmployeeAdmin.html for editing. This is where you'll do your work.
    1. In the getEmployeeList() function where the comment is:
      • open an asynchronous XMLHttpRequest using the GET method and the URL passed into the function.
      • When the readystate of the request changes, call the display() function with the appropriate parameters.
      • Send the request.
    2. In the display() function:
      • Set the innerHTML property of output div to the responseText of the returned xmlhttp object.
      • Test your solution to this piece by opening AjaxBasics/Exercises/EmployeeAdmin.html in your browser. You should see a list of the employees. If you do not, check your getEmployeeList() and display() functions.
    3. Finish the getEmployeeForm() function by setting the appropriate content type for submitting form data and sending the request.
    4. In the updateEmployee() function:
      • Write the code leading up to the call to send() method call.
      • When the readystate of the request changes, call the employeeUpdated() function.
  5. Test your solution in a browser by visiting http://localhost:8080/EmployeeAdmin.html

Challenge

In the employeeUpdated() callback function, we currently call getEmployeeList() to update the list of employees. This updates the entire list after each change. It would be better to just update the record that was changed. There is no need to make a call to the database to find out which record it was or how it was changed. The client already has that information. See if you can figure out how to remove this unnecessary Ajax call and use pure client-side JavaScript to update the list. Be careful not to update the list until you are sure that the record has been successfully updated in the database. The only file you will need to change is EmployeeAdmin.html.

Solution:

AjaxBasics/Solutions/EmployeeAdmin.html
---- C O D E   O M I T T E D ----

	function getEmployeeList(url) {
		var xmlhttp = new XMLHttpRequest();
		var output = document.getElementById("EmployeeList");
		output.innerHTML = "<h2>Loading...</h2>";
		xmlhttp.open("GET", url, true);
		xmlhttp.onreadystatechange = function() {
			display(output, xmlhttp);
		}
		xmlhttp.send(null);
	}

	function display(output, xmlhttp) {
		var employees, eid, target, i;
		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			output.style.display = "block";
			output.innerHTML = xmlhttp.responseText;

			if (xmlhttp.responseText.indexOf("Failed") == 0) {
				output.className = "Warning";
			} else {
				employees = output.getElementsByTagName("li");
				for (i = 0; i < employees.length; ++i) {
					observeEvent(employees[i], "click", function(e) {
						target = getTarget(e);
						eid = target.id.substring(3);
						getEmployeeForm("EmployeeForm", eid);
					});
				}
			}
		}
	}

	function getEmployeeForm(url, eid) {
		var xmlhttp = new XMLHttpRequest();
		var output = document.getElementById("FormDiv");
		var fields, field, value, i;
		output.innerHTML = "Loading...";
		xmlhttp.open("POST", url, true);
		xmlhttp.onreadystatechange = function() {
			display(output, xmlhttp);
			fields = output.getElementsByTagName("input");
			for (i = 0; i < fields.length; ++i) {
				observeEvent(fields[i], "change", function(e) {
					target = getTarget(e);
					field = target.name;
					value = target.value;
					updateEmployee("EditEmployee", field, value, eid);
				});
			}
		}
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("eid=" + eid);
	}

	function updateEmployee(url, field, value, eid) {
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("POST", url, true);
		xmlhttp.onreadystatechange = employeeUpdated;
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("eid=" + eid + "&field=" + field + "&value=" + value);

		function employeeUpdated() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				var msg = document.getElementById("MessageDiv");
				if (xmlhttp.responseText.indexOf("Failed") == 0) {
					msg.className = "Warning";
					msg.innerHTML = xmlhttp.responseText;
				} else {
					msg.innerHTML = "Updated!";
					getEmployeeList('EmployeeList');
				}
				fadeElem(msg, 255, 255, 0, 255, 255, 255);
			}
		}
	}
---- C O D E   O M I T T E D ----

Open http://localhost:8080/EmployeeAdmin.html in your browser to view the solution; be sure to start the Node.js server (by typing npm start from the command line) in the AjaxBasics/Solutions/ directory first.

Function getEmployeeList, invoked when the page loads, makes an Ajax call to /EmployeeList and invokes function display. If the Ajax calls is successful, then function display displays the returned list of employees in div#EmployeeList.

Also in function display we iterate over each of the bulleted names, listening for clicks. A click on any of the names results in a call to function getEmployeeForm, passing along the id of the clicked-upon employee.

Function getEmployeeForm, in turn, makes an Ajax request, via the POST method, to /EmployeeForm, displays the returned form, and adds change handlers on each of the fields of the form.

As a result of the handlers added in function getEmployeeForm, a change to any of the employee-form fields invokes function updateEmployee which, in turn, makes a POST Ajax request to /EditEmployee, sending along the id of the employee, field name, and field value. The function displays a success or failure message based on the status of the returned results.

Challenge Solution:

AjaxBasics/Solutions/EmployeeAdmin-challenge.html
---- C O D E   O M I T T E D ----

	function getEmployeeForm(url, eid) {
		var xmlhttp = new XMLHttpRequest();
		var output = document.getElementById("FormDiv");
		var fields, field, value, i, fname, lname;
		output.innerHTML = "Loading...";
		xmlhttp.open("POST", url, true);
		xmlhttp.onreadystatechange = function() {
			display(output, xmlhttp);
			fields = output.getElementsByTagName("input");
			for (i = 0; i < fields.length; ++i) {
				observeEvent(fields[i], "change", function(e) {
					target = getTarget(e);
					field = target.name;
					value = target.value;
					fname = target.form.FirstName.value;
					lname = target.form.LastName.value;
					updateEmployee("EditEmployee", field, value, eid, fname, lname);
				});
			}
		}
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("eid=" + eid);
	}

	function updateEmployee(url, field, value, eid, fname, lname) {
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("POST", url, true);
		xmlhttp.onreadystatechange = function() {
			employeeUpdated(eid, fname, lname);
		};
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("eid=" + eid + "&field=" + field + "&value=" + value);

		function employeeUpdated(eid, fname, lname) {
			var employee, msg;
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				msg = document.getElementById("MessageDiv");
				if (xmlhttp.responseText.indexOf("Failed") == 0) {
					msg.className = "Warning";
					msg.innerHTML = xmlhttp.responseText;
				} else {
					msg.innerHTML = "Updated!";
					employee = document.getElementById("emp" + eid);
					employee.innerHTML = fname + ' ' + lname;
				}
				fadeElem(msg, 255, 255, 0, 255, 255, 255);
			}
		}
	}
---- C O D E   O M I T T E D ----

Open http://localhost:8080/EmployeeAdmin-challenge.html in your browser to view the solution.

In function getEmployeeForm, we now keep track of the first and last name (from the fields with ids FirstName and LastName, respectively) of each employee. We pass these values as parameters in the call to updateEmployee when any of the fields is changed.

Function updateEmployee works largely as before but, upon successful update of the employee (i.e. a successful Ajax response code), the function now sets the displayed name of the relevant employee in the bulleted list.