Errors and AJAX

Thursday 19th of January 2017 09:37:44 AM In case you haven't heard by now, the hottest buzzword in the realm of web technology is AJAX (as coined in an Adaptive Path essay). The crux of the AJAX framework is the XMLHttpRequest JavaScript object which allows client-side developers to send and receive XML documents over HTTP without interrupting the user, and without hacking around with hidden frames. Now, some might shudder at the notion of allowing client-side developers who might be more used to validating forms and animating rollover images to suddenly be responsible for traversing XML documents and negotiating HTTP headers, but without risk, there is no reward. And just to soothe any lingering trepidation, I will demonstrate how to use XMLHttpRequest to not only add previously impossible or infeasible features, but also to reduce errors and improve quality.

XMLHttpRequest and XML DOM for JavaScript Basics

First, we need to set up a few ground rules. The XMLHttpRequest object in particular, and XML DOM in general, is widely supported in any recent browser (IE, Mozilla, Safari, Opera) although, as usual, Microsoft has taken a slightly different tack on implementation and requires some special care. While our more progressive friends directly implement XMLHttpRequest, IE requires that you instantiate an ActiveXObject with the same properties. An excellent overview and full feature list is available at the Apple Developer Connection site.

A basic example follows:

var req;
function postXML(xmlDoc) {
if (window.XMLHttpRequest) req = new XMLHttpRequest();
else if (window.ActiveXObject) req = new ActiveXObject("Microsoft.XMLHTTP");
else return; // fall on our sword
req.open(method, serverURI);
req.setRequestHeader('content-type', 'text/xml');
req.onreadystatechange = xmlPosted;
req.send(xmlDoc);
}
function xmlPosted() {
if (req.readyState != 4) return;
if (req.status == 200) {
var result = req.responseXML;
} else {
}
}

The potential uses for this powerful tool are vast, and exploration of the possibilities has just begun. But before anyone gets carried away with trying to create an XML circus on the web, I suggest we set up a safety net to keep any high-flyers from breaking their necks.

JavaScript Error Handling Basics

JavaScript has come a long way since its earlier versions, which were crude, lacking in features, and just poorly implemented. Newer browsers not only support the try/catch/finally keywords you will recognize from C++ and Java, they also implement an onerror event that can trap any error conditions that arise during runtime. Usage is pretty straightforward:

function riskyBusiness() {
try {
riskyOperation1();
riskyOperation2();
} catch (e) {
} finally {
}
}
window.onerror = handleError; // safety net to trap all errors
function handleError(message, URI, line) {
return true; // this will stop the default message
}

A Practical Example: Persisting Client-side Errors to your Server

Now that we have the basics of XMLHttpRequest and JavaScript error handling, let's look at an implementation that ties the two together. You'd think that JavaScript errors would be easy to spot given the prevalent "Yellow Triangle of Death", but I still see them slip past the QA departments of several blue chip organizations' public-facing web sites.

Figure 1

So, here I will present a method for trapping errors and logging them back to the server in the hope that someone might be alerted to fix it. First, let's consider our client. The client should provide a class to be used as a singleton Logger object that can transparently handle the gritty details.

First we create the constructor:

function Logger() {
this.req;
this.errorToXML = errorToXML;
this.log = log;
}

Next, we define the method that will serialize an Error object into XML. By default, an Error object only has two properties, name and message, but we will also check for a third called location which may be useful.

function errorToXML(err) {
var xml = '<?xml version="1.0"?>\n' +
'<error>\n' +
'<name>' + err.name + '</name>\n' +
'<message>'  + err.message + '</message>\n';
if (err.location) xml += '<location>' + err.location +
'</location>';
xml += '</error>';
return xml;
}

Next is the log method. This is the meat and potatoes of the script that really brings together the principles described above. Notice that we are using the POST method for our call. What I am essentially creating here is a bespoke web service that is write-only and creates new records on each successful request. Therefore, POST is the only appropriate option.

function log(err) {
if (window.XMLHttpRequest) this.req = new XMLHttpRequest();
else if (window.ActiveXObject) this.req =
new ActiveXObject("Microsoft.XMLHTTP");
else return; // throw up our hands in despair
this.req.open("POST", "/cgi-bin/AjaxLogger.cgi");
this.req.setRequestHeader('REFERER', location.href);
this.req.setRequestHeader('content-type', 'text/xml');
this.req.onreadystatechange = errorLogged;
this.req.send(this.errorToXML(err));
this.timeout = window.setTimeout("abortLog();", 10000);
}

The last part of our class definition is to create an instance of the Logger class. There should be only one instance of this class.

var logger = new Logger();

The last two functions are just there for housekeeping. If something goes wrong while logging the error, there is not much we can do except bother the user. Hopefully, it will never come to this. These are not class methods since the events will not have references to our object, but will refer to the logger instance we just created.

function abortLog() {
logger.req.abort();
alert("Attempt to log the error timed out.");
}
function errorLogged() {
if (logger.req.readyState != 4) return;
window.clearTimeout(logger.timeout);
if (logger.req.status >= 400)
alert('Attempt to log the error failed.');
}

All of the preceding code can be wrapped up into one .js file that can be included on any (or every) page in your site. Here is an and are next to each other in the document tree. For example:

H2 + P {color: silver;}

In the first set of markup, a paragraph immediately follows anH2, so it is silver. In the second, the elementadjacent to the H2 is a UL,which does not match the rule, and neither does the paragraph rightafter that. Finally, even though there is text directly after thethird H2, it isn't part of an element, sothe paragraph right after the text matches the rule and is colored example of how to include it and put it to good use:

<script type="text/javascript" src="Logger.js"></script>
<script type="text/javascript">
function trapError(msg, URI, ln) {
var error = new Error(msg);
error.location = URI + ', line: ' + ln; // add custom property
logger.log(error);
warnUser();
return true; // stop the yellow triangle
}
window.onerror = trapError;
function foo() {
try {
riskyOperation();
} catch (err) {
err.location = location.href + ', function: foo()';
logger.log(err);
warnUser();
}
}
function warnUser() {
alert("An error has occurred while processing this page."+
"Our engineers have been alerted!");
location.href = '/path/to/error/page.html';
}
</script>

Now that we have seen how to integrate the logger into our HTML pages, all that is left is to define some way of receiving and translating the message. I have chosen a lowest common denominator approach and built a CGI script in Perl that uses one of my favorite modules, XML::Simple, to parse the post data, and CGI::Carp to pipe the results directly to httpd error log, thus saving your system administrators from having to monitor another log. This script also includes some good examples of appropriate response codes for different success and failure conditions.

use CGI;
use CGI::Carp qw(set_progname);
use XML::Simple;
my $request = CGI->new();
my $method = $request->request_method();
# method must be POST
if ($method eq 'POST') {
eval {
my $content_type = $request->content_type();
if ($content_type eq 'text/xml') {
print $request->header(-status =>
'415 Unsupported Media Type', -type => 'text/xml');
croak "Invalid content type: $content_type\n";
}
# when method is POST and the content type is neither
# URI encoded nor multipart form, the entire post
# is stuffed into one param: POSTDATA
my $error_xml = $request->param('POSTDATA');
my $ref = XML::Simple::XMLin($error_xml);
my ($name, $msg, $location) =
($ref->{'name'}, $ref->{'message'}, '');
$location = $ref->{'location'} if (defined($ref->{'location'}));
# this will change the name of the carper in the log
set_progname('Client-side error');
my $remote_host = $request->remote_host();
carp "name: [$name], msg: [$msg], location: [$location]";
};
if ($@) {
print $request->header(-status => '500 Internal server error',
-type => 'text/xml');
croak "Error while logging: $@";
} else {
# this response code indicates that the operation was a
# success, but the client should not expect any content
print $request->header(-status => '204 No content',
-type => 'text/xml');
}
} else {
print $request->header(-status => '405 Method not supported',
-type => 'text/xml');
croak "Unsupported method: $method";
}

And that's all there is to it! Now, the next time some slippery JavaScript gets into the system, you can expect your log monitors to start flashing red lights and your client-side developers to get calls in the middle of the night.

Web-based applications are similar to app servers, except for one thing: Web-based applications don't have client apps, instead they use web browsers on the client side. They generate their front ends using HTML, which is dynamically generated by the web-based app. In the Java world, Servlets are best suited for this job.

Web-based apps might themselves rely on another app server to gather information that is presented on the client web browser. Also, you can write Servlets that get information from remote or local databases, XML document repositories and even other Servlets. One good use for web-based apps is to be a wrapper around an app server, so that you can allow your customers to access at least part of the services offered by your app server via a simple web browser. So web-based apps allow you to integrate many components including app servers, and provide access to this information over the web via a simple web browser.

Web-based apps are very deployable, since they don't require special Java VMs to be installed on the client side, or any other special plug ins, if the creator of the web-based app relies solely on HTML. Unfortunately, this can restrict the level of service that can be offered by a web-based app when compared to the functionality offered by custom clients of an app server, but they are a good compromise when it comes to providing web-based access to your information. In fact, in a real world scenario, both a web-based app and app server may be used together, in order to provide your customers access to their information. In an Intranet setting, you might deploy the clients that come with the app server, and in an Internet setting it would be better to deploy a web-based app that sits on top of this app server, and gives your customers (relatively) limited access to their data over the web (via a simple web browser).

Web-based apps and app servers integrate very well, and this is another reason why Java and XML make a powerful combination for developing systems that give your customers access to their information from anywhere, using any browser over the web. In the future, you can imagine various different web-based apps servicing different kinds of clients, e.g. web browsers on desktops, web browsers on PDAs, and web browsers on all kinds of different consumer electronics devices. By keeping your information structured in a pure way (by using XML), and by allowing access to this information through app servers, you can write many different web-based apps that render this information by customizing it uniquely for each different device that is allowed access to this information. This is more a more scalable solution that storing all this information in web pages, even if these web pages are dynamically generated. So you can have one app server that stores all the data in XML format. You can write a web-based app (which sits on top of this app-server) that allows PalmPilots to access this information over the web. You can write another web-based app (that also sits on top of the same app server) that allows conventional web browsers to access this information over the web. XML and Java have the potential to make this truly platform independent and device independent computing a reality.

API Coverage per category

400. As for the rest, 700 goes with bold as always, while 800 and 900, lacking a heavier face, are assigned to the Bold font face. Finally, 600 is assigned to the next-heavier face, which is, of course, the Bold face.

font-weight is inherited, so if you set a paragraph to be bold, then all of its children will inherit that boldness, as we see in Figure 5-9.

as wide as the paragraph's content area. The difference is made up in letter- and word-spacing, as we see in Figure 8-49.

Figure 8-49

Figure 8-49. Line-box layout with full justification

That pretty well covers how line boxes are generated, at least in the simplest cases. As we're about to see, however, the inline formatting model is not exactly simple.

LI.first {list-style-position: inside;}

This causes the bullet to be placed "inside" the list item's content. The exact way this happens is undefined, but Figure 7-86 shows one possibility.

Figure 7-86

Figure 7-86. Placing the bullets inside and outside list items

CSS2, by the way, provides a good deal more control over the positioning of the bullets (called "markers" in CSS2); again, this is discussed in Chapter 10, "CSS2: A Look Ahead".to make sure they always display on the same line, but you'll get little blank spaces between them.  A better alternative is to do multi-image layouts in tables (see separate chapter).

Pay attention to the file sizes of your images!  Web-surfers are often impatient, and may not wait for large images to load.  You can keep your visitors' attention with interleaved GIFs and progressive JPEG's, which appear quickly at coarse resolution and gradually sharpen, or you can referrence a small, low-resolution version of your image in the <IMG> tag, e.g. <IMG WIDTH=200 HEIGHT=150 SRC="hi_res.gif" LOWSRC="low_res.gif">