6 min read

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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here