Ajax with images

June 2nd, 2012. Tagged: Ajax, images, JavaScript

So you can do Ajaxy stuff with XMLHttpRequest or or iframes or dynamic JavaScript tags or... how about simple images. This is best used for simple stuff where you can have a limited number of predefined responses, such as "success" and "oops".

All you do is create an image and set its source and this makes a request:

new Image().src = "mystuff.php";

This is if you don't care about the response. If you want to inspect the response though you can attach onload and onerror handlers:

var i = new Image();
i.onload = function() {
  // inspect the response
};
i.src = "mystuff.php";

If you can assume you'll have "OK" response most of the time, you can have the .php return a 204 No Response which is the smallest response (no body). If the .php determines there's something wrong, it then can return an image.

When you send a 204 response, the onerror handler will be called because the response is not really an image. It looks backwards to have your success handler be called onerror, but if you expect more successes than errors, then it's probably worth it.

var i = new Image();
i.onload = function() {
  // an error occurred
};
i.onerror = function() {
  // success!
};
i.src = "mystuff.php";

And the final thing - if you want to have coded responses, in other words be able to differentiate between different errors (each with its error code), you can have the .php return different image sizes. Say with constant height but varying width. E.g. 1x1 image, 2x1, 3x1 and so on. In the onload you inspect the size of the image and determine the type of response.

var i = new Image();
i.onload = function() {
  // an error occurred
  if (i.width === 1) {
    // error #1
  } 
  if (i.width === 7) {
    // error #7
  } 
// etc...
 
};
i.onerror = function() {
  // success!
};
i.src = "mystuff.php";

I'm using a different errors as an example, but you can always have it the other way around: you consider onload a suceess and there are different types of successes.

Email address validation example

Let's take a look at a little more practical example. Let's validate email addresses on the server.

We'll return 7 different image sizes if the supplied email address is invalid or a 204 response is the email looks fine.

The OK response:

function ok() {
  header("HTTP/1.0 204 No Content");
}

The error response that generates an image with a desired width and height of 1 px:

function image($width) {
  header("Content-Type: image/png");
  $im = imagecreate($width, 1);
  imagecolorallocate($im, 0, 0, 0);
  imagepng($im);
  imagedestroy($im);
  die();
}

The error codes:

// 1x1 = empty input
// 2x1 = missing @
// 3x1 = too many @s
// 4x1 = missing username
// 5x1 = missing host
// 6x1 = blocked
// 7x1 = no dns record for host
// 204 = OK

And, finally, the "business" logic:

$email = (string)@$_GET['email'];
 
if (!$email) {
  image(1);
}
 
// split to username and domain
$splits = explode('@', $email);
 
if (count($splits) === 1) {
  image(2);
}
 
if (count($splits) !== 2) {
  image(3);
}
 
list($user, $host) = $splits;
 
if (!$user) {
  image(4);
}
 
if (!$host) {
  image(5);
}
 
$blocked = array('yahoo.com', 'gmail.com', 'hotmail.com');
 
if (in_array($host, $blocked)) {
  image(6);
}
 
 
if (!checkdnsrr($host)) {
  image(7);
}
 
// all fine, 204
ok();

Test page

You can play with the test page here:
http://www.phpied.com/files/imaje/test.html

The markup:

<input id="i"><button id="b">check email</button><pre id="r">enter an email</pre>

And the JS that makes a requests and checks for ok/error:

 
var i;
$('b').onclick = function() {
  i = new Image();
  i.onerror = function() {
    $('r').innerHTML = "thanks!";
  };
  i.onload = function() {
    $('r').innerHTML = "invalid email address!";
  };
  i.src = "imaje.php?email=" + encodeURIComponent($('i').value);
  $('r').innerHTML = "checking...";
};

All there is to it!

Verbose page

A more verbose test can inspect the width of the returned image and display an appropriate message to the user.

Play with it here:
http://www.phpied.com/files/imaje/verbose.html

var i;
$('b').onclick = function() {
  i = new Image();
  i.onerror = ok;
  i.onload = function() {
    err(i.width);
  }
  i.src = "imaje.php?email=" + encodeURIComponent($('i').value);
  $('r').innerHTML = "checking...";
};
 
function ok() {
  $('r').innerHTML = "this is one valid email address, good for you!";
}
 
function err(num) {
  var errs = [
    '',
    'Seriously, I need an email',
    "Where's the @?",
    "Too many @s",
    "Missing username",
    "Missing mail host",
    "BLOCKED! Go away!",
    "Not a valid mail server",
 
  ];
  $('r').innerHTML = errs[num];
}

A good side effect of using a global i is that async responses don't mess up the result. E.g. you send requests #1 and #2, #2 arrives first and is OK, #1 arrives later and is an error. At this point you've overwritten i and the handlers for #1 are no longer available. Dunno is it's possible but it would be cool to be able to further abort a previous request if you've made one after it.

Thanks

Thanks for reading! I know it's hardly new for you, my two faithful readers, but these responses with varying image size came up in a conversation recently and as it turns out there are rumors that there might be about 3 developers in Chibougamau, Quebec, that are not aware of this technique.

Tell your friends about this post: Facebook, Twitter, Google+

Sorry, comments disabled and hidden due to excessive spam. Working on restoring the existing comments...

Meanwhile, hit me up on twitter @stoyanstefanov