AJAX and events
Suppose we wanted to allow each dictionary term name to
control the display of the definition that follows; clicking on the term name
would show or hide the associated definition. With the techniques we have seen
so far, this should be pretty straightforward:
$(document).ready(function() {
$('.term').click(function() {
$(this).siblings('.definition').slideToggle(); }); });
When a term is clicked, this code finds siblings of the
element that have a class of definition, and slides them up or down as
appropriate.
All seems in order, but a click does nothing with this
code. Unfortunately, the terms have not yet been added to the document when we
attach the click handlers. Even if we managed to attach click
handlers to these items, once we clicked on a different letter the handlers
would no longer be attached.
This is a common problem with areas of a page populated
by AJAX. A popular solution is to rebind handlers each time the page area
is refreshed. This can be cumbersome, however, as the event binding code needs
to be called each time anything causes the DOM structure of the page to
change.
We can implement event delegation, actually
binding the event to an ancestor element that never changes. In this case, we'll
attach the click handler to the document using .live() and
catch our clicks that way:
$(document).ready(function() {
$('.term').live('click', function() {
$(this).siblings('.definition').slideToggle(); }); });
The .live() method tells the browser to observe
all clicks anywhere on the page. If (and only if) the clicked element matches
the .term selector, then the handler is executed. Now the toggling
behavior will take place on any term, even if it is added by a later AJAX
transaction.
Security limitations
For all its utility in crafting dynamic web
applications, XMLHttpRequest (the underlying browser technology behind jQuery's
AJAX implementation) is subject to strict boundaries. To prevent various
cross-site scripting attacks, it is not generally possible to request a
document from a server other than the one that hosts the original page.
This is generally a positive situation. For example,
some cite the implementation of JSON parsing by using eval() as insecure.
If malicious code is present in the data file, it could be run by the
eval() call. However, since the data file must reside on the same server
as the web page itself, the ability to inject code in the data file is largely
equivalent to the ability to inject code in the page directly. This means that,
for the case of loading trusted JSON files, eval() is not a
significant security concern.
There are many cases, though, in which it would be
beneficial to load data from a third-party source. There are several ways to
work around the security limitations and allow this to happen.
One method is to rely on the server to load the remote
data, and then provide it when requested by the client. This is a very powerful
approach as the server can perform pre-processing on the data as needed. For
example, we could load XML files containing RSS news feeds from several sources,
aggregate them into a single feed on the server, and publish this new file for
the client when it is requested.
To load data from a remote location without
server involvement, we have to get a bit sneakier. A popular approach for the
case of loading foreign JavaScript files is injecting <script> tags
on demand. Since jQuery can help us insert new DOM elements, it is simple to do
this:
$(document.createElement('script')) .attr('src',
'http://example.com/example.js') .appendTo('head');
In fact, the $.getScript() method will
automatically adapt to this technique if it detects a remote host in its URL
argument, so even this is handled for us.
The browser will execute the loaded script, but there is
no mechanism to retrieve results from the script. For this reason, the technique
requires cooperation from the remote host. The loaded script must take some
action, such as setting a global variable that has an effect on the local
environment. Services that publish scripts that are executable in this way will
also provide an API with which to interact with the remote script.
Another option is to use the <iframe> HTML
tag to load remote data. This element allows any URL to be used as the source
for its data fetching, even if it does not match the host page's server. The
data can be loaded and easily displayed on the current page. Manipulating the
data, however, typically requires the same cooperation needed for the
<script> tag approach; scripts inside the <iframe>
need to explicitly provide the data to objects in the parent document.
Using JSONP for remote data
The idea of using <script> tags to fetch
JavaScript files from a remote source can be adapted to pull in JSON files from
another server as well. To do this, we need to slightly modify the JSON file on
the server, however. There are several mechanisms for doing this, one of which
is directly supported by jQuery: JSON with Padding, or JSONP.
The JSONP file format consists of a standard JSON file
that has been wrapped in parentheses and prepended with an arbitrary text
string. This string, the "padding", is determined by the client requesting the
data. Because of the parentheses, the client can either cause a function to be
called or a variable to be set depending on what is sent as the padding
string.
A PHP implementation of the JSONP technique is quite
simple:
<?php print($_GET['callback'] .'('. $data
.')'); ?>
Here, $data is a variable containing a string
representation of a JSON file. When this script is called, the callback
query string parameter is prepended to the resulting file that gets returned to
the client.
To demonstrate this technique, we need only slightly
modify our earlier JSON example to call this remote data source instead. The
$.getJSON() function makes use of a special placeholder character,
?, to achieve this.
$(document).ready(function() { var url =
'http://examples.learningjquery.com/jsonp/g.php'; $('#letter-g
a').click(function() { $.getJSON(url + '?callback=?', function(data)
{ $('#dictionary').empty(); $.each(data, function(entryIndex,
entry) { var html = '<div class="entry">'; html +=
'<h3 class="term">' + entry['term'] +
'</h3>'; html += '<div class="part">' +
entry['part'] + '</div>'; html += '<div
class="definition">'; html += entry['definition']; if
(entry['quote']) { html += '<div
class="quote">'; $.each(entry['quote'], function(lineIndex,
line) { html += '<div class="quote-line">' +
line + '</div>'; }); if
(entry['author']) { html += '<div
class="quote-author">' + entry['author'] +
'</div>'; } html += '</div>';
} html += '</div>'; html +=
'</div>'; $('#dictionary').append(html); });
}); return false; }); });
We normally would not be allowed to fetch JSON from a
remote server (examples.learningjquery.com in this case). However, since this
file is set up to provide its data in the JSONP format, we can obtain the data
by appending a query string to our URL, using ? as a placeholder for the
value of the callback argument. When the request is made, jQuery replaces
the ? for us, parses the result, and passes it to the success function as
data just as if this were a local JSON request.
Note that the same security cautions hold here as
before; whatever the server decides to return to the browser will execute on the
user's computer. The JSONP technique should only be used with data coming from a
trusted source.
Additional options
The AJAX toolbox provided by jQuery is well-stocked.
We've covered several of the available options, but we've just scratched the
surface. While there are too many variants to cover here, we will give an
overview of some of the more prominent ways to customize AJAX
communications.
The low-level AJAX method
We have seen several methods that all initiate AJAX
transactions. Internally, jQuery maps each of these methods onto variants of the
$.ajax() global function. Rather than presuming one particular type of
AJAX activity, this function takes a map of options that can be used to
customize its behavior.
Our first example loaded an HTML snippet using
$('#dictionary').load('a.html'). This action could instead be
accomplished with $.ajax() as follows:
$.ajax({ url: 'a.html', type: 'GET',
dataType: 'html', success: function(data) {
$('#dictionary').html(data); } });
We need to explicitly specify the request method, the
data type that will be returned, and what to do with the resulting data.
Clearly, this is less efficient use of programmer effort; however, with this
extra work comes a great deal of flexibility. A few of the special capabilities
that come with using a low-level $.ajax() call include:
Ø
Preventing the browser from caching responses from the server.
This can be useful if the server produces its data dynamically.
Ø
Registering separate callback functions for when the request
completes successfully, with an error, or in all cases.
Ø
Suppressing the global handlers (such as ones registered with
$.ajaxStart()) that are normally triggered by all AJAX interactions.
Ø
Providing a user name and password for authentication with the
remote host.
For details on using these and other options, consult jQuery Reference
Guide or see the API reference online.
Modifying default options
The $.ajaxSetup() function allows us to specify
default values for each of the options used when AJAX methods are called. It
takes a map of options identical to the ones available to $.ajax()
itself, and causes these values to be used on all subsequent AJAX requests
unless overridden.
$.ajaxSetup({ url: 'a.html', type: 'POST',
dataType: 'html' }); $.ajax({ type: 'GET', success:
function(data) { $('#dictionary').html(data); } });
This sequence of operations behaves the same as our
preceding $.ajax() example. Note that the URL of the request is specified
as a default value by the $.ajaxSetup() call, so it does not have to be
provided when $.ajax() is invoked. In contrast, the type parameter
is given a default value of POST, but this can still be overridden in the
$.ajax() call to GET.
|