Chapter 9: Interactive Data on the World-Wide Web
9.2 Pulling Data from the Web
Many events on a Web page occur with relatively little user interaction, and are often driven by the availability of resources to your browser.
JavaScript can interact with remote servers and asynchronously retrieve information from them without holding up the display of a Web page.
Asynchronous JavaScript
Retrieving Data from Web Servers Asynchronously: AJAX
Requesting Data from Web Servers: REST APIs
Cross-Site Scripting
User Searches
Summary
Exercises
Asynchronous JavaScript
Loading a Web page in sequence can really slow it down, especially when it includes large images or a large data set, and it may even prevent the page from being usable in the interim. It helps to load these items after all others and attach them into the DOM at the end. But a better way to speed things up is to run pieces of your page construction code asynchronously, detaching it from the main processing and running it in parallel. It can then take advantage of delays that might hold up other parts of the page construction.
But how will you know when it’s safe to start executing a separate part of your code? By monitoring certain events, of course! When a browser has downloaded all of the static pieces of a page and everything is completely in place, it fires the load
event. But even earlier than that, when a browser has finished parsing the static framework of the DOM and before it has put other pieces into place, including stylesheets and images, it will fire the document’s DOMContentLoaded
event, and that’s often enough to allow other activities to proceed.
jQuery provides a method to monitor the DOMContentLoaded
event, $(document).ready()
, and when it occurs it responds by running a callback function of your choice. For example, our map’s framework looks like:
<script src="js/jquery-2.2.1.min.js"></script>
<div id="nepm2">
<svg id="neSVG" viewBox="-59619 82359 643486 810982">
<g id="new_england_states" transform="translate(0,810982) scale(1, -1)"></g>
</svg>
<div id="neTiles"></div>
</div>
and its pieces are installed with JavaScript that doesn’t have to wait for the rest of the page before beginning its work:
<script src="scripts/jquery-2.2.1.min.js"></script>
<script src="scripts/ne_states.js"></script>
<script>
$(document).ready(
function() {
appendTiles($('#neTiles'),
'images/chapter6_3/Dodge_s_Geography_of_New_England%2009_Fig_3/', 3, 4);
appendPaths($('#new_england_states'), ne_states, 'id');
}
);
</script>
Note the use of an anonymous function here, which is very common for short pieces of code, as event handlers often are.
Be aware, when you read other people’s code, that there are synonyms for $(document).ready(function() { '....' })
, viz. $().ready(function() { '....' })
or even more simply $(function() { '....' })
, which suggests the importance of this technique. However, its purpose is not as readily apparent, especially in the last format.
Retrieving Data from Web Servers Asynchronously: AJAX
Think about requests for updates that might be generated by a user operating the controls on a Web map: clicking a button to zoom in and out, double-clicking on a location to center it, clicking and dragging the mouse to pan across the map.For each user action, information is sent to the map server and it sends back a new view of the map, which the browser must process appropriately. It used to be that the browser would reload the entire page to show the new view! But it is much faster to just update the map pieces as necessary — for example loading new tiles and sliding others around. Being able to process data separately from other pieces of a Web page makes interactive Web applications much more usable.
So far we have been loading our data by embedding them within JavaScript code and referencing them with <script>
elements, e.g.
<script src="ne_states.js"></script>
⇒ var ne_states = [
{ "id": "Maine", "STATE_FIPS": "23", "STATE_ABBR": "ME", "d": '....' },
'....'
];
We could use document.createElement('script').setAttribute('src', URL)
to download additional data asynchronously, but there is a better approach when talking with a same Web server that originated the page.
The JavaScript Object Notation used here is very general, so it’s possible that it could be referenced and used by something other than JavaScript, if we left out the opening assignment var ne_states =
and trailing ;
.
There are also other common data formats available on the Web, such as Comma-Separated Values (CSV) files, as well as many eXtensible Markup Language (XML) documents, a language specification that includes HTML (mostly) and SVG.
AJAX
To download any of these formats we need a different mechanism than the <script>
element. The first one to come along, in the late 1990s, was designed for Asynchronous JavaScript and XML, and is therefore known as AJAX, even though it can be applied to other formats, too.
The asynchronous nature of AJAX is an important feature on the Web, where the time it takes to download data can vary tremendously from server to server and from one time to another.
The basic AJAX process is to create an object XMLHttpRequest
that requests data with the standard Web protocol HTTP, waits and listens for a response, processes the data, and notifies an event handler that it’s available for use. It might look something like the following when downloading this JSON file:
var data_url = 'https://cschweik.gitbooks.io/community-service-with-web-based-gist/content/scripts/ne_states.json';
var data_request = new XMLHttpRequest();
data_request.onload = // Do something with the result
function () {
if (this.status == 200) // Indicates page request OK
window.alert('Success: ' + this.response);
else // Could have loaded an error page from the server.
data_request.onerror();
}
data_request.onerror = // Signal an error in the process
function () {
console.log('Failure: ' + this.status + ' ' + this.statusText);
}
data_request.open('GET', data_url);
data_request.send(null);
If you load the course Web book (i.e. if you are reading this online!), open its console, and run this code, it will execute either the load
handler or the error
handler, and when they have completed their task, the method window.alert()
will open a dialog to display the response:
[
{ "id": "Maine", "STATE_FIPS": "23", "STATE_ABBR": "ME", "d": "...."},
"...."
]
or perhaps
Failure: 0
Here’s what’s happening:
- The JavaScript object
data_request
is created with the keywordnew
and the object constructorXMLHttpRequest()
; - An event handler is assigned to
data_request.onload
to do something with the result; - An event handler is assigned to
data_request.onerror
to do something in case of failure; - The method
data_request.open()
opens a communication channel to the data location, which includes a request toGET
the contents ofdata_url
(more aboutGET
later); - The method
data_request.send()
sends anull
datum to indicate that there’s no more information to send; - The object
data_request
then waits for the browser to download and process the data, firing the eventload
when complete orerror
if there is a problem (e.g. an incorrect server or a dropped connection); - A successful load means that a page was returned from the server — but it might be a server error message, e.g. the requested document might not be found, the common
404
error. This is one of many standard HTTP result codes returned by the server. If the document is found and there aren’t other errors (e.g. you don’t have privilege to access that document), the return status is200
. - Whether there is a load or a failure, however long that takes, a message is displayed using
window.alert()
.
Try this in your browser’s console! Also try changing the URL’s server name or document slightly so they’re incorrect and see what response you get!
Warning: While the 'Success'
message probably appears very quickly, it is still asynchronous, and any processing of the result must occur via the load
callback function to ensure that it has become available for use, e.g. written into the page or stored in a database.
As written above the method data_request.onload()
just displays the text from the file data_url
. Since we know the response represents a JSON array of objects, we can request that it be parsed into JavaScript by setting the property data_request.responseType = 'json'
before opening the connection:
var data_url = 'https://cschweik.gitbooks.io/community-service-with-web-based-gist/content/scripts/ne_states.json';
var data_request = new XMLHttpRequest();
data_request.responseType = 'json';
data_request.onload = // Do something with the result
function () {
if (this.status == 200) // Indicates page request OK
window.alert('Success: ' + this.response.length + ' objects received.');
else // Could have loaded an error page from the server.
data_request.onerror();
}
data_request.onerror = // Signal an error in the process
function () {
window.alert('Failure: ' + this.status + ' ' + this.statusText);
}
data_request.open('GET', data_url);
data_request.send(null);
jQuery.ajax()
It’s probably not surprising that jQuery makes this process somewhat simpler, by grouping the different settings together into an object that’s handed to the method $.ajax()
:
var data_url = 'https://cschweik.gitbooks.io/community-service-with-web-based-gist/content/scripts/ne_states.json';
var data_request = $.ajax({
url: data_url,
method: 'GET',
dataType: 'json'
})
.done( function (data, textStatus, request) {
window.alert('Success: ' + data.length + ' objects received.');
})
.fail( function (request, textStatus, errorThrown) {
console.log('Failure: ' + request.status + ' ' + errorThrown);
});
The jQuery .fail()
method includes not only failure to connect to the server or dropped connections, but also common server-side issues such as 404 Not Found
, which still produce a successfully loaded Web page and so otherwise would need to be tested for in the load
handler as in the previous DOM-direct example. You can therefore assume that the done()
method produces the data you are expecting (if the URL is correct).
The method $.ajax()
also provides backward and cross-browser compatibility by implementing work-arounds where necessary, so its use is recommended.
You can learn even more about both XMLHttpRequest and $.ajax(), including variations on their use, some of which we’ll see later.
Requesting Data from Web Servers: REST APIs
Web servers are able to respond to much more than simple page requests, including additional queries for information that they can analyze to determine specific content to return.
These queries are sent as a set of key-value pairs appended to the end of a URL.
We previously saw examples of these queries such as the source of Dodge’s Geography of New England, https://books.google.com/books?id=-DREAQAAMAAJ, and our Geoserver setup, which understands searches such as http://nrcwg01.eco.umass.edu:8080/geoserver/wms?service=WMS&version=1.1.0&request=GetMap&layers=tiger-ny&bbox=-74.047185,40.679648,-73.907005,40.882078&width=531&height=768&srs=EPSG:4326&format=application/openlayers.
The query portion of the URL begins with the?
character, and is followed by a series of key=value pairs, each set separated by &
. The map data is extracted, and the returned map is displayed, based on this information.
Such requests are transmitted from the browser to the server using an HTTP method known as GET
, which is generally considered a “safe” method because it simply requests information.
Other possible methods generally involve transmitting content to the server that it will incorporate some how, e.g. the HTTP method POST
. We’ll learn more about this later.
Web servers are all built to understand these HTTP methods and interpret these queries, establishing a uniform interface across the World-Wide Web.
The information sent to the server that is necessary to produce the intended result, known as state, is expected to be completely included in the transmission. The data returned to the client is a snapshot of the server data determined by that “representational state”. This interface standard is therefore known as REpresentational State Transfer (REST), and applications that follow it are called RESTful.
REST goes hand-in-hand with asynchronous JavaScript, so that the latter can send off a request to a server, wait for the response, and when it comes back process it without having to wait for anything else.
There are many RESTful Web applications providing data, for example OpenStreetMap and the Census Bureau.
The particular set of queries that these applications respond to are known as their Application Programming Interface (API). Often they will require an individual API key that must be included with every request, e.g. &key=3cd81b792b50d13f3896
.
Retrieving OpenStreetMap Data
As a simple example of a RESTful API, consider this request for information from OpenStreetMap’s geocoding service, Nominatim, which you can enter directly into your Web browser’s address field:
http://nominatim.openstreetmap.org/search?q=Boston,+Massachusetts&format=json
Here we see two key=value pairs q=Boston,+Massachusetts
(using the +
as a substitute for a space character) and format=json
.
The Nominatim server searches for matching geographic information in its database, and formats the result as an array of JSON objects (here of length one):
⇒ [{
"place_id":"144586664",
"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
"osm_type":"relation",
"osm_id":"2315704",
"boundingbox":["42.227654","42.3969775","-71.1912599","-70.804488"],
"lat":"42.3604823",
"lon":"-71.0595677",
"display_name":"Boston, Suffolk County, Massachusetts, United States of America",
"class":"place",
"type":"city",
"importance":0.94997821709893,
"icon":"http:\/\/nominatim.openstreetmap.org\/images\/mapicons\/poi_place_city.p.20.png"
}]
You’ll recognize most of the properties in this object, but some others won’t be familiar. The importance
parameter is used to sort the output from most (1) to least (0); you could combine it with the limit
property to get the same result from a less specific search q=Boston
:
http://nominatim.openstreetmap.org/search?q=Boston&limit=1&format=json
The osm_type
is either:
- a node (a single point);
- a way (a polyline or polygon); or
- a relation (a combination of the above);
The osm_id
is unique across OpenStreetMap when combined with osm_type
, and can be used to reverse look-up the location:
http://nominatim.openstreetmap.org/reverse?osm_id=2315704&osm_type=R&format=json
⇒ {"....", "display_name":"Boston, Suffolk County, Massachusetts, United States of America", "...."}
You can also request information with latitude and longitude, but this may not return the feature you expect:
http://nominatim.openstreetmap.org/reverse?lat=42.3604823&lon=-71.0595677&format=json
⇒ {
"place_id":"3151835",
"....",
"osm_type":"node",
"osm_id":"367779563",
"...."
"display_name":"Government Center, Sudbury Street, Dock Square, West End, Boston, Suffolk County, Massachusetts, 02114, United States of America",
"address":{"public_building":"Government Center","road":"Sudbury Street","neighbourhood":"Dock Square","suburb":"West End","city":"Boston","county":"Suffolk County","state":"Massachusetts","postcode":"02114","country":"United States of America","country_code":"us"},
"...."
}
Retrieving Census Data
Another potentially more complex example involves retrieving census data. The API is not well-documented, but the Census Bureau provides many examples. Here’s a simple one that looks up the total population of the New England states from the American Community Survey’s 5-year data ending in 2014:
http://api.census.gov/data/2014/acs5?for=state:09,23,25,33,44,50&get=NAME,B01003_001E
⇒
[["NAME","B01003_001E","state"],
["Connecticut","3592053","09"],
["Maine","1328535","23"],
["Massachusetts","6657291","25"],
["New Hampshire","1321069","33"],
["Rhode Island","1053252","44"],
["Vermont","626358","50"]]
The for
keyword lists the geographies to include, using the state FIPS numbers to identify them, separated by commas. You could also use *
to select all states (*
is commonly used as a wildcard character, matching all possibilities).
The get
keyword lists the particular topic to include for each geography, in this case its NAME
and the data from column 001E
of table B01003
, providing Total Population
. The two-digit state FIPS code is also appended.
The returned result is JSON, though in a format that closely matches CSV.
An additional keyword, in
, can be used to limit the extent of the geography, and then generic geographies can be used with for
. For example, to search for county population in Massachusetts (FIPS 25), they do not have to be explicitly listed:
http://api.census.gov/data/2014/acs5?in=state:25&for=county&get=NAME,B01003_001E
⇒
[["NAME","B01003_001E","state","county"],
["Barnstable County, Massachusetts","215167","25","001"],
["Berkshire County, Massachusetts","130064","25","003"],
["Bristol County, Massachusetts","551065","25","005"],
["Dukes County, Massachusetts","16915","25","007"],
["Essex County, Massachusetts","757395","25","009"],
["Franklin County, Massachusetts","71300","25","011"],
["Hampden County, Massachusetts","466447","25","013"],
["Hampshire County, Massachusetts","160328","25","015"],
["Middlesex County, Massachusetts","1539832","25","017"],
["Nantucket County, Massachusetts","10414","25","019"],
["Norfolk County, Massachusetts","682860","25","021"],
["Plymouth County, Massachusetts","500772","25","023"],
["Suffolk County, Massachusetts","747928","25","025"],
["Worcester County, Massachusetts","806804","25","027"]]
The result matches any county in the state of Massachusetts, and lists their name as well as their 3-digit county FIPS code.
The best way to locate the data that you want is to use the American Factfinder. You’ll need to keep in mind that only some topics are available for all geographies, and all topics are available for only some geographies. The choice of year and the data program (e.g. ACS or decennial census) will also restrict what’s available. Once you’ve found what you’re looking for, you’ll also see a set of tables, many of which can be used in the API queries:
Determining the columns available in a particular table may be best accomplished by searching for a table in this list of variables. While it has all of the available tables, it can be harder to choose between similar tables since they are not necessarily grouped together.
Cross-Site Scripting
When your scripts start pulling data like census or address information from other servers on the Web, it is said to be engaged in Cross-Site Scripting (XSS), and the other servers will view it as a security risk. The basic issue is that when a page is loaded from one Web site, and that page’s scripts start communicating with another site, it can potentially bring with it hidden code to attack the second server.
Same-Origin Policy
This possibility gives rise to the same-origin policy, meaning that by default Web servers will only talk to their own scripts unless they are explicitly set to allow others.
You can see cross-origin access control in action by trying to execute the above AJAX scripts referencing https://cschweik.gitbooks.io
in the console of any other Web site, where you’ll get messages like:
XMLHttpRequest cannot load https://cschweik.gitbooks.io/community-service-with-web-based-gist/content/scripts/ne_states.json. Origin http://server is not allowed by Access-Control-Allow-Origin.
In other words, the server cschweik.gitbooks.io
has refused to talk with the originating Web site.
Thankfully, many Web sites such as Nominatim are set up to allow public XSS access. A direct way to tell is to open the Web site, open the console, and run this script that prints out the HTTP response header, which contains metadata about the content and which is transferred along with it:
var data_request = new XMLHttpRequest();
data_request.open('GET', document.URL, false);
data_request.send(null);
data_request.getAllResponseHeaders();
⇒
"Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS,GET
Content-Length: 2185
Content-Type: text/html; charset=UTF-8
Server: Apache/2.4.7 (Ubuntu)
Date: ...
...
"
The property document.URL
is the URL of the current document, so the script is running same-site and won’t be blocked. The header field Access-Control-Allow-Origin: *
says that the server will accept requests from any origin (again, *
is a wildcard character). The field Access-Control-Allow-Methods: OPTIONS,GET
restricts those requests to only the GET
method we’ve been using and the OPTIONS
method that can be used to ask for information.
Same-Protocol Policy
There is also a second, related constraint that may appear when communicating across platforms, which is intended to protect user information. If a script’s originating page is using a secure connection via HTTPS, the browser may not allow it to pull down data from a second server over an insecure connection via HTTP (this is specifically true of the Safari browser). But if the second server allows HTTPS connections, you can simply use that protocol in your script.
Taking the latter into account, a general script to make a Nominatim request looks like:
var data_request = $.ajax({
url: 'https://nominatim.openstreetmap.org/search',
method: 'GET',
dataType: 'json',
data: { q: 'Boston, Massachusetts', format: 'json' }
})
.done( function (data, textStatus, request) {
window.alert('Success: ' + data[0].display_name);
})
.fail( function ( request, textStatus, errorThrown) {
console.log('Failure: ' + request.status + ' ' + errorThrown);
});
⇒
Success: Boston, Suffolk County, Massachusetts, United States of America
Note that, unlike the original URL which had q=Boston,+Massachusetts
, here an ordinary space is used instead of the +
. One other advantage of jQuery is that it will automatically encode characters embedded in data values that have special meaning in URLs such as :
, /
, ?
, &
, and +
, so that they will not be interpreted as special. The encoding is simply the %
sign followed by their Unicode hexadecimal value, e.g. +
becomes %2B
, and the space character becomes %20
.
JSON with Padding
Many Web servers are not set up to allow cross-site scripting, but still respond to <script href="...js"></script>
references, since these are strictly downloads. These do, however, require that JavaScript statements be sent rather than just data. Such servers will therefore usually facilitate this process by “wrapping” the data inside of a specified function, responding to requests like &callback=processJSON
or &jsonp=processJSON
with a download that looks like a JavaScript statement, e.g. processJSON(data)
. This practice is known as JSON with Padding (JSONP).
The course GeoServers provide an important example of JSONP, as they are not able to send the Access-Control-Allow-Origin
header. But when they are configured to send JSONP, one of the options on the Layer Preview page is WFS:GeoJSON(JSONP)
, which generates a GeoJSON object at the following URL:
http://nrcwg01.eco.umass.edu:8080/geoserver/massachusetts/ows
?service=WFS
&version=1.0.0
&request=GetFeature
&typeName=massachusetts:towns
&maxFeatures=50
&outputFormat=text%2Fjavascript
⇒
parseResponse(
{
"type": "FeatureCollection",
"totalFeatures": 611,
"features": [
{
"type": "Feature",
"id": "towns.1",
"geometry": {
"type":"MultiPolygon",
"coordinates": [
[
[
[256497.15359999985,951401.6209000014]
/* , additional coordinates */
]
/* , additional coordinate rings (“holes” in first) */
]
/* , additional polygons */
]
},
"geometry_name": "the_geom",
"properties": {
"OBJECTID": 71,
"TOWN_ID": 330,
"TOWN": "WESTFORD"
/* , additional properties */ }
}
/* , additional features */
],
"crs": {
"type": "name",
"properties": {"name":"urn:ogc:def:crs:EPSG::26986"}
}
}
The difference between this JSONP request and a regular JSON request is the key-value pair outputFormat=text/javascript
in the former and outputFormat=application/json
in the latter.
jQuery will facilitate the use of JSONP by using the same $.ajax()
function with the addition of certain parameters:
var rootUrl = 'http://nrcwg01.eco.umass.edu:8080/geoserver/massachusetts/ows';
var typeName = 'massachusetts:towns';
var geoServerParameters = {
service: 'WFS',
version: '1.0.0',
request: 'GetFeature',
typeName: typeName,
maxFeatures: 50,
outputFormat: 'text/javascript'
};
var data_request = $.ajax({
url: rootUrl,
method: 'GET',
dataType: 'jsonp',
jsonpCallback: 'parseResponse',
jsonp: false,
data: geoServerParameters
})
.done( function (data, textStatus, request) {
window.alert('Number of features: ' + data.totalFeatures);
})
.fail( function ( request, textStatus, errorThrown) {
console.log('Failure: ' + request.status + ' ' + errorThrown);
});
⇒
Number of features: 611
Some details:
- The parameter
dataType: 'jsonp'
tells jQuery to use<script src="....">
rather thanXMLHttpRequest()
. - The value of
jsonpCallback
must match the callback function name the server uses to pad the data; - By default jQuery wants to inform the server that JSONP is requested by including the semi-standard query
'&callback=' + jsonpCallback
, but settingjsonp: false
prevents this, as GeoServer instead uses&outputFormat=text/javascript
. - Once jQuery retrieves the data, it runs its own implementation of the specified callback function to process it into JSON and then calls your
.done()
function as before.
User Searches
With these JavaScript tools we have just seen, a simple user geographic search can be constructed, using another type of <input>
, a text field:
🔍 <input type="text" id="osmSearch" value="Search Location" />
<table id="osmSearchResults"></table>
🔍
Try out this text field! You should get something like this:
display_name | importance | type | class | lat | lon | osm_id | osm_type |
---|---|---|---|---|---|---|---|
Amherst, Hampshire County, Massachusetts, United States of America | 0.5608744604851 | administrative | boundary | 42.3803676 | -72.5231429 | 1839150 | relation |
The field uses value
as the initial text in the box, and if that changes so does value
.
The script handles two events, the first responding to the field receiving focus, i.e. entry into it whether by clicking on it or by tabbing into it. It simply clears the current value
, so the user doesn’t have to before they start typing:
function valueClear()
{
this.value = '';
}
$('#osmSearch').focus(valueClear)
The second handler activates if the field changes its value and the user types the tab key or the enter key, or clicks outside of the field:
function osmSearch()
{
var string = escapeHtml(this.value);
if (string == '')
{
this.value = 'Search Location';
return;
}
$.ajax({
url: 'https://nominatim.openstreetmap.org/search',
method: 'GET',
dataType: 'json',
data: { q: string, format: 'json', limit: 20 }
})
.done(generateLookupTable)
.fail( function ( request, textStatus, errorThrown) {
window.alert('Failure: ' + request.status + ' ' + errorThrown);
});
};
$('#osmSearch').change(osmSearch)
The input value
is first made HTML-safe by passing it through this filter that converts HTML special characters into character entities, each beginning with &
and ending with ;
, and which will be understood only as regular characters:
// Safely turn interpretable HTML characters into plain text
// From http://benv.ca/2012/10/02/you-are-probably-misusing-DOM-text-methods/
function escapeHtml(string) {
return String(string)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/\//g, "/")
}
The numeric character entities are references to their Unicode values, either as decimal values &#...;
or as hexidecimal values &#x...;
.
The notation /pattern/flag
is known as a regular expression, and is a very powerful tool for finding text. Here it only finds those single characters anywhere in the document, and the g
or “global” flag matches more than one per line.
If the string is empty, the initial value of the text field is restored, and the function returns having done nothing else.
Otherwise, the string is passed to an AJAX call to Nominatim; there is a default limit of 10 return values, but this call sets it to 20 instead.
If the lookup is successful, the done
handler is called, which is the function generateLookupTable()
. This is similar to the previous table constructors we’ve seen, but first removes any earlier results before recreating the table with new ones:
function generateLookupTable(data)
{
// Removes existing table rows, if any.
$('#osmSearchResultsHead').remove();
$('#osmSearchResultsBody').remove();
var cols = [ 'display_name', 'importance', 'type', 'class', 'lat', 'lon', 'osm_id', 'osm_type'];
var thead = $('<thead>').attr('id', 'osmSearchResultsHead');
var tr = $('<tr>');
for (var col = 0; col < cols.length; col++)
tr.append(
$('<th>').text(cols[col])
);
thead.append(tr);
$('#osmSearchResults').append(thead);
var tbody = $('<tbody>').attr('id', 'osmSearchResultsBody');
for (var row = 0; row < data.length; row++)
{
var tr = $('<tr>');
for (var col = 0; col < cols.length; col++)
tr.append(
$('<td>').text(data[row][cols[col]])
);
tbody.append(tr);
}
$('#osmSearchResults').append(tbody);
}
Summary
- Web page creation can be sped up in many cases by letting JavaScript code run asynchronously, in response to events generated as the browser loads the page.
- When the static framework of the DOM is in place, the browser will fire the document’s
DOMContentLoaded
event, which will initiate a callback function in jQuery’s method$('document').ready()
. - When downloading data from a Web server, it will often come formatted as JSON, CSV, or XML.
- Asynchronous downloading of data can be initiated using the
XMLHttpRequest
object, which fires various events that can be monitored and handled on completion, a process known as AJAX. jQUuery’s method$.ajax()
somewhat simplifies this process. - RESTful Web servers provide Application Programming Interfaces (APIs) that can be used to request data.
- Data requests from a server different than the originating Web site are generally blocked to prevent Cross-Site Scripting (XSS) attacks.
Exercises
- Turn the Census URL population request into a working script, but for the Massachusetts towns. Some details:
- The best match in the examples list is for “state› county› county subdivision”. Make sure you get it working in your Web browser first!
- You don’t need an API key for a small request like this.
- You will need to add the parameter
cache: true
to make it work properly with jQuery (otherwise jQuery adds a random key-value pair to the request to avoid caching the page, which the Census Bureau rejects). - While the URL uses
county+subdivision
, with the+
character representing the space character, in the jQuery data list this value should be written as simplycounty subdivision
, to avoid encoding the+
and correctly encode the space.
- Building on the search example above, provide a similar search for locations from the Census Bureau, using their geocoder. Extra: add a population column using data gathered with a lookup using exercise #1. (Warning: if you use this geocoder on a server rather than in your browser, it will require the use of JSONP.)