facebook google plus twitter
Webucator's Free Ajax Tutorial

Lesson: Passing Data

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

Lesson Goals

  • Send and receive XML to and from the server.
  • Send and receive JSON to and from the server.
  • Update the DOM based on server responses.

XML

In addition to the responseText property, XMLHttpRequest objects also have a responseXML property, which holds the data returned from the server as an XML document object (or null if the body content returned is not well-formed XML).

The properties and methods for accessing and inserting nodes into HTML documents also apply to XML documents. They are shown below:

Properties for Accessing Element Nodes
Property Description
documentElement The root element of the document (applicable to the document object, not to the descendant nodes).
childNodes[] A nodeList containing all of a node's child nodes.
firstChild A reference to a node's first child node. Same as childNodes[0].
lastChild A reference to a node's last child node. Same as childNodes[childNodes.length-1].
nextSibling A reference to the next node at the same level in the document tree.
parentNode A reference to a node's parent node.
previousSibling A reference to the previous node at the same level in the document tree.
Methods for Inserting Nodes
Method Description
appendChild() Takes a single parameter: the node to insert, and inserts that node after the last child node.
insertBefore() Takes two parameters: the node to insert and the child node that it should precede. The new child node is inserted before the referenced child node.
replaceChild() Takes two parameters: the new node and the node to be replaced. It replaces the old node with the new node and returns the old node.
setAttribute() Takes two parameters: the attribute name and value. If the attribute already exists, it replaces it with the new value. If it doesn't exist, it creates it.

The demo below shows how to load an XML document, to read parts of it into a string and then output that string as HTML to a part of the page.

To run the demo, first:

  1. From the command line (on a PC) or terminal (on a Mac), navigate to the directory PassingData/Demos/.
  2. Type npm install to load the required Node.js modules.
  3. Type npm start to start the server.
  4. Open http://localhost:8080/XML.html in a browser.

Code Sample:

PassingData/Demos/XML.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>XML</title>
<link href="xml.css" type="text/css" rel="stylesheet">
<script type="text/javascript" src="lib.js"></script>
<script type="text/javascript">
	function init() {
		var btnLoad = document.getElementById("btnLoad");
		observeEvent(btnLoad, "click", loadXml);
	}

	function loadXml() {
		var btnDisplay = document.getElementById("btnDisplay");
		var output = document.getElementById("output");
		var xmlhttp = new XMLHttpRequest();
		var xmlDoc;

		xmlhttp.open("GET", "Beatles.xml", true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				output.innerHTML = "XML Loaded";
				xmlDoc = xmlhttp.responseXML;
			}
		}
		xmlhttp.send(null);

		observeEvent(btnDisplay, "click", function() {
			var btnClear = document.getElementById("btnClear");
			addNodes(xmlDoc.documentElement);
			observeEvent(btnClear, "click", clearXml);
		});
	}

	function addNodes(node, parent) {
		var output = document.getElementById("output");
		var child, cls;
		var list, listitem, sublist;

		if (!parent) {
			output.innerHTML = "";
			list = document.createElement("ol");
			output.appendChild(list);
		} else {
			list = parent;
		}

		for (var i = 0; i < node.childNodes.length; i++) {
			child = node.childNodes[i];
			cls = (child.nodeType == 3) ? "textnode" : "nontextnode";

			listitem = document.createElement("li");
			listitem.className = cls;
			listitem.innerHTML = child.nodeName + ": " + child.nodeValue;
			list.appendChild(listitem);

			if (child.hasChildNodes()) {
				sublist = document.createElement("ol");
				listitem.appendChild(sublist);
				addNodes(child, sublist);
			}
		}

		if (node.nodeType == 1) { //Element Node
			for (var i = 0; i < node.attributes.length; i++) {
				listitem = document.createElement("li");
				listitem.className = "attribute";
				listitem.innerHTML = node.attributes[i].nodeName + ": " + node.attributes[i].nodeValue;
				list.appendChild(listitem);
			}
		}
	}

	function clearXml() {
		document.getElementById("output").innerHTML = "XML Cleared";
	}

	observeEvent(window, "load", init);
</script>
</head>
<body>
	<button id="btnLoad">Load XML</button>
	<button id="btnDisplay">Display XML</button>
	<button id="btnClear">Clear XML</button>
	<div id="output"></div>
</body>
</html>
  1. The page loads and shows three buttons: "Load XML", "Display XML", and "Clear XML".
  2. Clicking the "Load XML" button, the request for the XML document is made and the XML document is returned as the screenshot below shows: XML Document Returned Notice that the XML has not yet been incorporated into the page, but you can see in Firebug at the bottom of the browser that the XML has indeed been returned.
  3. Clicking the "Display XML" button builds an HTML list node by node: HTML List Node by Node
  4. Clicking the "Clear XML" button changes the innerHTML property of the output div to an empty string, thereby removing the list from the page. This does not remove the XML document from memory. You can see this by clicking on the "Load XML" button again.

Creating an addChild() Function

Adding nodes to the DOM can be a lot of work. It can involve several steps, including:

  1. Creating a new element node.
  2. Appending the element node as a child of another element.
  3. Creating a new text node.
  4. Appending the text node to the new element node.
  5. Adding one or more attributes with the setAttribute() method of the new element.

The user-defined function below (included in our lib.js file) makes this process easier.

function addChild(doc, parent, child, childText, attributes) { if (typeof child == "string") { childNode = doc.createElement(child); } else { childNode = child; } if (typeof childText == "string") { childTextNode = doc.createTextNode(childText); childNode.appendChild(childTextNode); } if (attributes) { for (var att in attributes) { childNode.setAttribute(att,attributes[att]); } } parent.appendChild(childNode); return childNode; }

This addChild() function requires the first three parameters: the DOM document, parent-to-be node, and child-to-be (either an existing node or the name of the node-to-be). The function can also take the text that will become the contents of the new child and an attributes object for the new child. The demo below shows how the function is called:

Code Sample:

PassingData/Demos/addChild.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="lib.js"></script>
<script type="text/javascript">
	observeEvent(window, "load", function() {
		var atts = {
			"style": "background:red;color:white;",
			"align": "center"
		}
		addChild(document, document.getElementsByTagName("body")[0], "p", "Hello World!", atts);
	});
</script>
<title>Add Child Demo</title>
</head>
<body>
<h1>Add Child Demo</h1>
</body>
</html>

Using addChild()

Duration: 10 to 15 minutes.

In this exercise, you will modify the XML.html file we saw earlier to use our new addChild() function, which is in the already included lib.js file.

  1. Open PassingData/Exercises/XML.html in your editor.
  2. Navigate to PassingData/Exercises/ from the command line and type npm install to install the needed Node.js modules.
  3. Type npm start from the command line to start the Node.js server.
  4. Wherever you see an opportunity, modify the code to use the addChild() function.
  5. Open http://localhost:8080/XML.html in your browser to test your solution.

Solution:

PassingData/Solutions/XML.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>XML</title>
<link href="xml.css" type="text/css" rel="stylesheet">
<script type="text/javascript" src="lib.js"></script>
<script type="text/javascript">
	function init() {
		var btnLoad = document.getElementById("btnLoad");
		observeEvent(btnLoad, "click", loadXml);
	}

	function loadXml() {
		var btnDisplay = document.getElementById("btnDisplay");
		var output = document.getElementById("output");
		var xmlhttp = new XMLHttpRequest();
		var xmlDoc;

		xmlhttp.open("GET", "Beatles.xml", true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				output.innerHTML = "XML Loaded";
				xmlDoc = xmlhttp.responseXML;
			}
		}
		xmlhttp.send(null);

		observeEvent(btnDisplay, "click", function() {
			var btnClear = document.getElementById("btnClear");
			addNodes(xmlDoc.documentElement);
			observeEvent(btnClear, "click", clearXml);
		});
	}

	function addNodes(node, parent) {
		var output = document.getElementById("output");
		var child, cls;
		var list, listitem, sublist;

		if (!parent) {
			output.innerHTML = "";
			,list = addChild(document, output, "ol");,
		} else {
			list = parent;
		}

		for (var i = 0; i < node.childNodes.length; i++) {
			child = node.childNodes[i];
			cls = (child.nodeType == 3) ? "textnode" : "nontextnode";

			,listitem = addChild(document, list, "li", child.nodeName + ": " + child.nodeValue);,
			listitem.className = cls;

			if (child.hasChildNodes()) {
				sublist = addChild(document, listitem, "ol");
				addNodes(child, sublist);
			}
		}

		if (node.nodeType == 1) { //Element Node
			for (var i = 0; i < node.attributes.length; i++) {
				listitem = addChild(document, list, "li", node.attributes[i].nodeName + ": " + node.attributes[i].nodeValue);
				listitem.className = "attribute";
			}
		}
	}

	function clearXml() {
		document.getElementById("output").innerHTML = "XML Cleared";
	}

	observeEvent(window, "load", init);
</script>
---- C O D E   O M I T T E D ----

Start the Node.js server from the PassingData/Solutions/ directory to view our solution: from the command line, type npm install to install the needed Node.js modules, then type npm start. Then open http://localhost:8080/XML.html in your browser.

The init function, invoked when the page loads, adds a click listened on the "Load XML" button; clicking the button invokes the loadXml function.

The loadXml function makes an Ajax call to Beatles.xml - if the call is successful, then a message ("XML Loaded") is displayed, the received XML is stored in variable xmlDoc, and a click listener is added to the "Display XML" button.

Clicking the "Display XML" button invokes function addNodes, passing along the received XML data; addNodes iterates over the XML and displays in the div with id output.

Dynamic Tables

We have seen how to get XML data from the server and display it in the browser. Let's now see how we can use data returned in this way to populate an HTML table.

From the command line/terminal, navigate to the directory PassingData/Demos/ and type npm start, then open http://localhost:8080/Table.html in your browser. When it first loads, the page has a simple button that reads "Show Table." When the button is clicked, the table shows up on the page: Table on Page

The page works as follows:

  1. When the user clicks on the button, JavaScript is used to make a call to Employees, served from the Employees response route in server.js.
  2. The Employees response route in Node.js makes a call to the database, which returns a recordset of all employees in the Employees table. That recordset is looped through to create and return an XML document to the browser. The structure of the XML document is shown below:XML Document Structure(The nodes for Employees 3 through 9 are collapsed in the diagram above.)
  3. As soon as the browser receives and completely loads the response, it iterates through the Employee nodes creating an HTML table and then displays that table on the page.

Let's take a look at the code.

Code Sample:

PassingData/Demos/Table.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Dynamic Table</title>
<link rel="stylesheet" type="text/css" href="Table.css">
<script type="text/javascript" src="lib.js"></script>
<script type="text/javascript">
	function buildTable() {
		removeAllChildren(document.getElementById("HeaderRow"));
		removeAllChildren(document.getElementById("BodyRows"));
		createHeaderRow();
		getRows();
	}

	function createHeaderRow() {
		var headerRow = document.getElementById("HeaderRow");
		var colNames = ["Salesperson", "Title", "Birth Date", "Hire Date", "Extension"];
		var colWidths = [24, 24, 21, 21, 8];

		for (var i = 0; i < colNames.length; i++) {
			var width = colWidths[i];
			var atts = new Object();
			atts["style"] = "width:" + width + "%;";
			addChild(document, headerRow, "th", colNames[i], atts);
		}
	}

	function getRows() {
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("get", "Employees", true);
		xmlhttp.onreadystatechange = function() {
			if (this.readyState == 4 && this.status == 200) {
				showResult(this);
			}
		};
		xmlhttp.send(null);
	}

	function showResult(xmlhttp) {
		var xmlDoc = xmlhttp.responseXML.documentElement;
		var tableName = "employees";
		var rowElement = "Employee";
		var outputResult, rowData;

		removeWhitespace(xmlDoc, true);

		outputResult = document.getElementById("BodyRows");
		rowData = xmlDoc.getElementsByTagName(rowElement);
		addTableRowsFromXmlDoc(rowData, outputResult);
	}

	function addTableRowsFromXmlDoc(xmlNodes, tableNode) {
		var theTable = tableNode.parentNode;
		var newRow, newCell, i, j;

		for (i = 0; i < xmlNodes.length; i++) {
			newRow = tableNode.insertRow(i);

			newRow.className = (i % 2) ? "OddRow" : "EvenRow";

			for (j = 0; j < xmlNodes[i].childNodes.length; j++) {
				newCell = newRow.insertCell(newRow.cells.length);
				newCell.innerHTML = decodeURIComponent(xmlNodes[i].childNodes[j].firstChild.nodeValue);
			}
		}
		theTable.appendChild(tableNode);
	}

	observeEvent(window, "load", function() {
		var btnEmployees = document.getElementById("btnEmployees");
		observeEvent(btnEmployees, "click", buildTable);
	});
</script>
</head>
<body>
<button id="btnEmployees">Show Table</button>
<table cellpadding="2" cellspacing="0" id="MainTable">
	<thead>
		<tr id="HeaderRow"></tr>
	</thead>
	<tbody id="BodyRows"></tbody>
</table>
</body>
</html>

Let's walk through the code step by step in the order the page is processed.

  1. The initial HTML in the body of the page looks like this:
    <button id="btnEmployees">Show Table</button> <table cellpadding="2" cellspacing="0" id="MainTable"> <thead> <tr id="HeaderRow"></tr> </thead> <tbody id="BodyRows"></tbody> </table>
    We'll use JavaScript/Ajax to populate the table header row (HeaderRow) and table body rows (BodyRows).
  2. When the page loads, we attach the buildTable() function to the click event of the btnEmployees button:
    observeEvent(window,"load",function() { var btnEmployees = document.getElementById("btnEmployees"); observeEvent(btnEmployees,"click", buildTable); });
  3. The buildTable() function is shown below:
    function buildTable() { removeAllChildren(document.getElementById("HeaderRow")); removeAllChildren(document.getElementById("BodyRows")); createHeaderRow(); getRows(); }
    This function uses the removeAllChildren() function from our lib.js library to empty out the table header row and all the table body rows. It then calls createHeaderRow() and getRows(). We'll examine those next.
  4. createHeaderRow() looks like this:
    function createHeaderRow() { var headerRow = document.getElementById("HeaderRow"); var colNames = ["Salesperson","Title","Birth Date", "Hire Date","Extension"]; var colWidths = [24,24,21,21,8]; for (var i=0; i<colNames.length; i++) { var width=colWidths[i]; var atts = new Object(); atts["style"] = "width:" + width + "%;"; addChild(document, headerRow, "th", colNames[i], atts); } }
    This function creates arrays to hold the column names (colNames) and widths (colWidths) and then loops through the colNames array using the addChild() function from our lib.js library to insert th elements in our table header row.
  5. getRows() takes care of the XMLHttpRequest:
    function getRows() { var xmlhttp = new XMLHttpRequest(); xmlhttp.open("get", "Employees", true); xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { showResult(this); } }; xmlhttp.send(null); }
  6. The showResult() function is shown below:
    function showResult(xmlhttp) { var xmlDoc = xmlhttp.responseXML.documentElement; var tableName = "employees"; var rowElement = "Employee"; var outputResult, rowData; removeWhitespace(xmlDoc,true); outputResult = document.getElementById("BodyRows"); rowData = xmlDoc.getElementsByTagName(rowElement); addTableRowsFromXmlDoc(rowData,outputResult); }
    The first line of the function stores the document element of the DOM Document in the xmlDoc variable. After declaring some other variables, we remove all whitespace-only text nodes using the removeWhitespace() function from our lib.js library. The next three lines store the table body in the outputResult variable and the XML nodeList in the rowData variable and pass both to the addTableRowsFromXmlDoc() function.
  7. The addTableRowsFromXmlDoc() function is shown below:
    function addTableRowsFromXmlDoc(xmlNodes,tableNode) { var theTable = tableNode.parentNode; var newRow, newCell, i; for (i=0; i<xmlNodes.length; i++) { newRow = tableNode.insertRow(i); newRow.className = (i%2) ? "OddRow" : "EvenRow"; for (j=0; j<xmlNodes[i].childNodes.length; j++) { newCell = newRow.insertCell(newRow.cells.length); newCell.innerHTML = xmlNodes[i].childNodes[j].firstChild.nodeValue; } } theTable.appendChild(tableNode); }
    This function loops through the passed-in XML nodeList, adding a new table row for each node in the list. It then sets the class name of the row. The inner loop iterates through all the child nodes (e.g, Salesperson, Title, etc.) of the current node (e.g, the current Employee), creating a table data cell for each.

Handling responseXML

Duration: 20 to 30 minutes.

In this exercise, you will modify the Table.html file we have just been discussing so that it has two buttons, one for showing employees and the other for showing shipping companies. The shipping data will be retrieved from the Node.js server; to see the XML, visit http://localhost:8080/Shippers in your browser. The XML will be structured like this:XML Structure

  1. If needed, navigate to the directory PassingData/Exercises/ from the command line and type npm start to start the Node.js server.
  2. Open PassingData/Exercises/Table.html for editing.
  3. Create a global variable called tableDataOptions holding an object with the following properties:
    • employees (object):
      • tableDataSrc: "Employees"
      • rowElement: "Employee"
      • columnNames (array):
        • "Salesperson"
        • "Title"
        • "Birth Date"
        • "Hire Date"
        • "Extension"
      • columnWidths (array):
        • 24
        • 24
        • 21
        • 21
        • 8
    • shippers (object):
      • tableDataSrc: "Shippers"
      • rowElement: "Shipper"
      • columnNames (array):
        • "CompanyName"
        • "Phone"
        • "ShipperID"
      • columnWidths (array):
        • 45
        • 41
        • 14
    • active: "employees"
    The employees and shippers properties hold the data for getting and outputting those tables. The active property holds the table to currently display.
  4. Modify the buildTable() function so that it takes a single argument: table and sets the active property of tableDataOptions to the lowercased passed-in table name.
  5. Change the page so that it displays two buttons reading "Employees" and "Shippers". When the user clicks on the Employees button, the employees should be listed on the page. When the user clicks on the Shippers button, the shippers should be listed.
  6. You will need to modify the getRows(), showResult(), and createHeaderRow() functions so that they work off the "active" table.
  7. Test your solution by opening PassingData/Exercises/Table.html in a browser.

Challenge

When the tables load, there is sometimes a delay that might make the user wonder what's happening. Provide a "Loading" message that shows up when the user clicks the link and goes away when the table appears.

Solution:

PassingData/Solutions/Table.html
---- C O D E   O M I T T E D ----

<script type="text/javascript">
	var tableDataOptions = {
		employees: {
			tableDataSrc: "Employees",
			rowElement: "Employee",
			columnNames: ["Salesperson", "Title", "Birth Date", "Hire Date", "Extension"],
			columnWidths: [24, 24, 21, 21, 8]
		},
		orders: {
			tableDataSrc: "Orders",
			rowElement: "Order",
			columnNames: ["Salesperson", "Customer", "Order ID", "Order Date"],
			columnWidths: [29, 34, 14, 23]
		},
		active: "employees"
	};

	function buildTable(table) {
		tableDataOptions["active"] = table.toLowerCase();
		removeAllChildren(document.getElementById("HeaderRow"));
		removeAllChildren(document.getElementById("BodyRows"));
		createHeaderRow();
		getRows();
	}

	function getRows() {
		var tableName = tableDataOptions["active"];
		var tableDataSrc = tableDataOptions[tableName].tableDataSrc;
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("get", tableDataSrc, true);
		xmlhttp.onreadystatechange = function() {
			if (this.readyState == 4 && this.status == 200) {
				showResult(this);
			}
		};
		xmlhttp.send(null);
	}

	function showResult(xmlhttp) {
		var xmlDoc = xmlhttp.responseXML.documentElement;
		var tableName = tableDataOptions["active"];
		var rowElement = tableDataOptions[tableName].rowElement;
		var outputResult, rowData;

		removeWhitespace(xmlDoc, true);
		outputResult = document.getElementById("BodyRows");
		rowData = xmlDoc.getElementsByTagName(rowElement);

		addTableRowsFromXmlDoc(rowData, outputResult);
	}

	function createHeaderRow() {
		var headerRow = document.getElementById("HeaderRow");
		var tableName = tableDataOptions["active"];
		var colNames = tableDataOptions[tableName].columnNames;
		var colWidths = tableDataOptions[tableName].columnWidths;

		for (var i = 0; i < colNames.length; i++) {
			var width = colWidths[i];
			var atts = new Object();
			atts["style"] = "width:" + width + "%;";
			addChild(document, headerRow, "th", colNames[i], atts);
		}
	}

	function addTableRowsFromXmlDoc(xmlNodes, tableNode) {
		var theTable = tableNode.parentNode;
		var newRow, newCell, i;

		for (i = 0; i < xmlNodes.length; i++) {
			newRow = tableNode.insertRow(i);

			newRow.className = (i % 2) ? "OddRow" : "EvenRow";

			for (j = 0; j < xmlNodes[i].childNodes.length; j++) {
				newCell = newRow.insertCell(newRow.cells.length);
				newCell.innerHTML = decodeURIComponent(xmlNodes[i].childNodes[j].firstChild.nodeValue);
			}
		}
		theTable.appendChild(tableNode);
	}

	observeEvent(window, "load", function() {
		var btnEmployees = document.getElementById("btnEmployees");
		var btnOrders = document.getElementById("btnOrders");
		observeEvent(btnEmployees, "click", function() {
			buildTable("Employees");
		});
		observeEvent(btnOrders, "click", function() {
			buildTable("Orders");
		});
	});
</script>
</head>
<body>
	<h1>Please choose a table to show:</h1>
	<button id="btnEmployees">Employees</button>
	<button id="btnOrders">Orders</button>

---- C O D E   O M I T T E D ----

Challenge Solution:

PassingData/Solutions/Table-challenge.html
---- C O D E   O M I T T E D ----

	function buildTable(table) {
		tableDataOptions["active"] = table.toLowerCase();
		removeAllChildren(document.getElementById("HeaderRow"));
		removeAllChildren(document.getElementById("BodyRows"));
		showLoadingMessage();
		createHeaderRow();
		getRows();
	}
	
---- C O D E   O M I T T E D ----

	function showResult(xmlhttp) {
		var xmlDoc = xmlhttp.responseXML.documentElement;
		var tableName = tableDataOptions["active"];
		var rowElement = tableDataOptions[tableName].rowElement;
		var outputResult, rowData;

		removeWhitespace(xmlDoc, true);
		outputResult = document.getElementById("BodyRows");
		rowData = xmlDoc.getElementsByTagName(rowElement);

		addTableRowsFromXmlDoc(rowData, outputResult);
		hideLoadingMessage();
	}
	
---- C O D E   O M I T T E D ----

	function showLoadingMessage() {
		var loadingDiv = document.getElementById("LoadingMessage");
		loadingDiv.style.visibility = "visible";
		document.bgColor = "#9999cc";
	}

	function hideLoadingMessage() {
		var loadingDiv = document.getElementById("LoadingMessage");
		loadingDiv.style.visibility = "hidden";
		document.bgColor = "#ffffff";
	}
	
---- C O D E   O M I T T E D ----

	<div id="LoadingMessage">Loading...</div>

---- C O D E   O M I T T E D ----
>

JSON

JSON is a lightweight format for exchanging data between the client and server. It is often used in Ajax applications because of its simplicity and because its format is based on JavaScript object literals. We will start by reviewing JavaScript's object-literal syntax and then we will see how we can use JSON in an Ajax application.

Review of Object Literals

Arrays

Array literals are created with square brackets as shown below:

var beatles = ["Paul","John","George","Ringo"];

This is the equivalent of:

var beatles = new Array("Paul","John","George","Ringo");

Objects

Object literals are created with curly brackets:

var beatles = { "Country" : "England", "YearFormed" : 1959, "Style" : "Rock'n'Roll" }

This is the equivalent of:

var beatles = new Object(); beatles.Country = "England"; beatles.YearFormed = 1959; beatles.Style = "Rock'n'Roll";

Just as with all objects in JavaScript, the properties can be references using dot notation or bracket notation.

alert(beatles.Style); //Dot Notation alert(beatles["Style"]); //Bracket Notation

Arrays in Objects

Object literals can contain array literals:

var beatles = { "Country" : "England", "YearFormed" : 1959, "Style" : "Rock'n'Roll", "Members" : ["Paul","John","George","Ringo"] }

Objects in Arrays

Array literals can contain object literals:

var rockbands = [ { "Name" : "Beatles", "Country" : "England", "YearFormed" : 1959, "Style" : "Rock'n'Roll", "Members" : ["Paul","John","George","Ringo"] }, { "Name" : "Rolling Stones", "Country" : "England", "YearFormed" : 1962, "Style" : "Rock'n'Roll", "Members" : ["Mick","Keith","Charlie","Bill"] } ]

Back to JSON

On the JSON website (http://www.json.org), JSON is described as:

  1. "a lightweight data-interchange format"
  2. "easy for humans to read and write"
  3. "easy for machines to parse and generate"

Numbers 1 and 3 are certainly true. Number 2 depends on the type of human. Experienced programmers will find that they can get comfortable with the syntax relatively quickly.

JSON Syntax

The JSON syntax is like JavaScript's object literal syntax except that the objects cannot be assigned to a variable. JSON just represents the data itself. So, the Beatles object we saw earlier would be defined as follows:

{ "Name" : "Beatles", "Country" : "England", "YearFormed" : 1959, "Style" : "Rock'n'Roll", "Members" : ["Paul","John","George","Ringo"] }

JSON Parsers

As JSON is just a string of text and not an object in and of itself, it needs to be converted to an object to make it useful. Although this can be done in JavaScript with the eval() function, it is safer to use a JSON parser. You can download the JavaScript JSON parser at http://www.json.org/json.js. Including this file on a web page allows you to take advantage of the JSON object, which has the following very useful methods:

  • JSON.parse(strJSON) - converts a JSON string into a JavaScript object.
  • JSON.stringify(objJSON) - converts a JavaScript object into a JSON string.

The process for sending data between the browser and server with JSON is as follows:

  1. On the client-side:
    • Create a JavaScript object using the standard or literal syntax.
    • Use the JSON parser to stringify the object.
    • Send the URL-encoded JSON string to the server as part of the HTTP Request. This can be done using the HEAD, GET or POST method by assigning the JSON string to a variable. It can also be sent as raw text using the POST method, but this may create extra work for you on the server-side.
  2. On the server-side:
    • Convert the incoming JSON string to an object using a JSON parser for the language of your choice. At http://www.json.org, you'll find JSON parsers for many modern programming languages. The methods available depend upon which parser you are using. See the parser's documentation for details.
    • Do whatever you wish with the object.
    • If you wish to send JSON back to the client:
      • Create a new object for storing the response data.
      • Convert the new object to a string using your JSON parser.
      • Send the JSON string back to the client as the response body (e.g, Response.Write(strJSON), echo $strJSON, out.write(strJSON) etc.).
  3. On the client-side:
    • Convert the incoming JSON string to an object using the JavaScript JSON parser.
    • Do whatever you wish with the object.
    • And so on...

The examples below show how to transfer data to the server using JSON.

Code Sample:

PassingData/Demos/SendJson.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="lib.js"></script>
<script type="text/javascript" src="json.js"></script>
<script type="text/javascript">
	function sendRequest(msg) {
		document.getElementById("ResponseDiv").innerHTML = "";
		var objJSON = {
			"msg": msg
		};
		var strJSON = encodeURIComponent(JSON.stringify(objJSON));
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("post", "ReceiveJSON", true);

		xmlhttp.onreadystatechange = function() {
			respond(xmlhttp);
		}

		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("strJSON=" + strJSON);
	}

	function respond(xmlhttp) {
		document.getElementById("ResponseDiv").innerHTML = xmlhttp.responseText;
	}

	observeEvent(window, "load", function() {
		var btnHi = document.getElementById("hi");
		var btnBye = document.getElementById("bye");
		observeEvent(btnHi, "click", function() {
			sendRequest(btnHi.innerHTML);
		});
		observeEvent(btnBye, "click", function() {
			sendRequest(btnBye.innerHTML);
		});
	});
</script>
<title>Using JSON</title>
</head>

<body>
<h1>Request</h1>
<button id="hi">Hi There!</button>
<button id="bye">Good bye!</button>
<h1>Response</h1>
<div id="ResponseDiv">Waiting...</div>
</body>
</html>

The server-side end of this process is the ReceiveJSON response route in server.js. In response to a POST request, it responds with either "And hi there to you!" (if the request parameter strJSON's msg field has value "Hi There!") or "Later Gator!" (if the msg field has any other value).

This code is relatively simple. The client-side script:

  • creates a simple JSON object with one property holding the passed in MSG string.
  • converts the JSON object to a string and encodes it.
  • passes the string as a parameter of our Ajax request.
  • outputs the responseText to the page.

The server-side script:

  • decodes the passed in string and converts it to a JSON object.
  • outputs an appropriate response.

JSON Advantages and Disadvantages

Advantages

JavaScript Syntax

The fact that JSON uses JavaScript syntax is the biggest advantage. It makes it extremely easy to work with on the client side. Compare this to traversing the XML DOM or XSLT and you'll find great efficiencies in working with JSON.

Brevity

Another oft-touted advantage of JSON is that it is lightweight. Compare, for example, the rockbands object we saw earlier in the lesson in JSON vs. XML:

//JSON [ { "Name" : "Beatles", "Country" : "England", "YearFormed" : 1959, "Style" : "Rock'n'Roll", "Members" : ["Paul","John","George","Ringo"] }, { "Name" : "Rolling Stones", "Country" : "England", "YearFormed" : 1962, "Style" : "Rock'n'Roll", "Members" : ["Mick","Keith","Charlie","Bill"] } ] //XML <Rockbands> <Rockband> <Name>Beatles</Name> <Country>England</Country> <YearFormed>1959</YearFormed> <Members> <Member>Paul</Member> <Member>John</Member> <Member>George</Member> <Member>Ringo</Member> </Members> </Rockband> <Rockband> <Name>Rolling Stones</Name> <Country>England</Country> <YearFormed>1962</YearFormed> <Members> <Member>Mick</Member> <Member>Keith</Member> <Member>Charlie</Member> <Member>Bill</Member> </Members> </Rockband> </Rockbands>

The XML representation has almost twice as many characters. While for long files, this may be significant, in most cases this will not cause any noticeable difference. Further, if you were to represent the XML with attributes rather than elements, the difference would be much less.

Disadvantages

New Syntax - Not So Human Readable

The biggest disadvantage of working with JSON is it is yet another syntax we need to familiarize ourselves with. However, with a little effort, most developers will find it's not too difficult to get comfortable with.

Not "XPathable"

XPath makes it possible to search through XML documents and find specific node values based on many different criteria (e.g, relative location to other nodes in the document). JavaScript objects and arrays have no comparable built-in capability.

Using JSON

Duration: 30 to 40 minutes.

In this exercise, you will create an Ajax Quiz that uses JSON to pass data to the server.

  1. From the command line, navigate to the directory PassingData/Exercises/ and type npm start to start the Node.js server.
  2. Open PassingData/Exercises/AjaxQuiz.html in your editor.
  3. Review the HTML. Notice the questions are named "q1", "q2", and "q3" and that each question has an associated result div.
  4. When the page loads, we call the init() function, which attaches the checkAnswer() function to click events of all the input elements in the form.
  5. Write the checkAnswer() function. It should do the following:
    1. Create an object with two properties: question and answer, that hold the question number (e.g, "q3") and the user's answer (e.g, "3").
    2. Pass that object to the JSON parser to stringify. That should result in a string like: {"question" : "q3","answer" : "3"}
    3. Encode that, and store it in a new object as the strJSON property.
    4. Write out "checking..." to the appropriate result div while awaiting a response from the server.
    5. Using Ajax, send the data to the server to be processed by /AjaxQuiz, which expects a strJSON parameter to be posted.
    6. The callback function is respond() and expects two parameters: the XMLHttpRequest object and the result div.
  6. Write the respond() function so that it outputs the server response text to the appropriate result div.
  7. Test your solution in a browser by visiting http://localhost:8080/AjaxQuiz.html.

Challenge

Modify the quiz so that all questions are processed at once:

  1. Save AjaxQuiz.html as AjaxQuiz-challenge.html. Add a button at the bottom of the HTML form that, when clicked, calls checkQuiz() and passes it the form object.
  2. The checkQuiz() function should create a string with the following format:
    { "answers" : [a1, a2, a3] }
    where a1, a2, and a3 hold the user's answers, or "x" if the question is not answered. For example, if the user answers 3 for the first question, 2 for the second question, and leaves the third question unanswered, the JSON string should look like this: { "answers" : ["1", "3", "x"] }. The function should then pass that string to the /AjaxQuiz-challenge Node.js response route, which is already written.
  3. The server-side script will return a JSON string with the following format:
    {"q1":"Wrong","q2":"Right","q3":"Unanswered"}
  4. Based on the string the server returns, write out the server responses (e.g, "Wrong") to the q1Result, q2Result, and q3Result divs.

Solution:

PassingData/Solutions/AjaxQuiz.html
---- C O D E   O M I T T E D ----

	function respond(xmlhttp, resultDiv) {
		resultDiv.innerHTML = xmlhttp.responseText;
	}

	function checkAnswer(e) {
		var target = getTarget(e);
		var q = target.name;
		var a = target.value;
		var objQuestion = {
			question: q,
			answer: a
		};
		var resultDiv = document.getElementById(q + "Result");
		var strJSON = encodeURIComponent(JSON.stringify(objQuestion));
		resultDiv.innerHTML = "checking...";
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("post", "AjaxQuiz", true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				respond(xmlhttp, resultDiv);
			}
		}
		xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("strJSON=" + strJSON);
	}

	observeEvent(window, "load", init);
</script>
---- C O D E   O M I T T E D ----

Challenge Solution:

PassingData/Solutions/AjaxQuiz-challenge.html
---- C O D E   O M I T T E D ----

	function init() {
		var quiz=document.getElementById("quizForm");
		var sbtButton = document.getElementById("sbtButton");
		observeEvent(sbtButton,"click",function() {
			checkQuiz(quiz);
		});
	}
	
	function respond(xmlhttp) {
		var strJSON = decodeURIComponent(xmlhttp.responseText);
		var objJSON = JSON.parse(strJSON);
		for (i in objJSON) {
			document.getElementById(i + "Result").innerHTML = objJSON[i];
		}
	}
	
	function checkQuiz(form) {
		var a1 = getAnswer(form.q1);
		var a2 = getAnswer(form.q2);
		var a3 = getAnswer(form.q3);
		var objJSON = {
			"answers" : [a1, a2, a3]
		}
		var strJSON = JSON.stringify(objJSON);
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.open("post","AjaxQuiz-challenge",true);
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
				respond(xmlhttp);
			}
		}
		xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
		xmlhttp.send("strJSON=" + encodeURIComponent(strJSON));
	}
	
	function getAnswer(radio) {
		for (var i=0; i<radio.length; i++) {
			if (radio[i].checked) {
				return radio[i].value;
			}
		}
		return null;
	}
	
	observeEvent(window,"load",init);
</script>
---- C O D E   O M I T T E D ----

<input type="button" id="sbtButton" name="Submit" value="Check Answers">
</form>
</body>
</html>