Monday, May 31, 2010

Improving the performance of REGEXP queries in MySql

Say we’re trying to query a table of UK postcode and just want to return the postcodes in a particular area(the Birmingham area B for instance). A naive implementation of this may be something like this

SELECT * FROM Postcodes WHERE Postcode LIKE 'B%'

This works for some postcodes but doesn’t work for this particular example because it also returns any postcodes in the BA, BB, BT etc areas. So it looks like a regular expression is required, to ensure the area code is followed by a number, so we try the following

SELECT * FROM Postcodes WHERE Postcode REGEXP'^B[0-9]'

This works and returns what we expect but it is much slower than the LIKE query. So is there any way to speed it up? Actually, it’s pretty straightforward, just combine the LIKE and the REGEXP queries, like so

SELECT * FROM Postcodes WHERE Postcode LIKE 'B%' AND Postcode REGEXP'^B[0-9]'

This give MySql the chance to first filter the data based on the LIKE clause then only use the regular expression on the filtered data. It’s not quite as fast as the original LIKE implementation but it’s much quicker than REGEXP on its own and unlike the original LIKE implementation it actually works.

Sunday, May 30, 2010

The Fibonacci Sequence

For Christmas 1987, my brother gave me ‘yet another interesting book’ (his words), “The Penguin Dictionary of Curious and Interesting Numbers”. Clearly it must be pretty interesting, since I was surprised to see it is still available. Being something of a maths geek, I did enjoy reading it, but it has been languishing in my loft for a few years now.

Jump forward a couple of decades and I was reading “The Rabbit Problem” as a bedtime story to my daughter and was wondering why it was set in Fibonacci’s Field. Clearly this was related to the Fibonacci Sequence (and reading the back cover gave the game away) but I was unsure how the Fibonacci Sequence was related to rabbits. So time to climb up into the loft to find out. Turns out the Fibonacci Sequence was the solution to a problem about rabbits breeding. And it also appears a lot in nature, specifically in the number of petals etc in plants.

As if that wasn’t enough, the ratios of successive terms of the Fibonacci Sequence tend towards the Golden Ratio, another interesting number that you can find out about in the dictionary. And here’s a little Javascript example to show that convergence.


Free XML sitemap generator with unlimited pages

I’ve been happily using this free XML sitemap generator for a while, but as my sites have got bigger I keep hitting the 500 page limit. Since I’m too tight to actually pay for this kind of service I’ve been searching around for a while for another free alternative. I was even thinking of writing my own, but before I was forced down that road, I found this alternative generator. I’m not too keen on Java applets generally, but this one seems to work very well. If it fails to download any pages, you can go back and retry downloading the failed pages. It also gives details of how long pages take to download, so it can also be a useful performance checker of a website.

Even so, I’m a little ambivalent towards sitemaps, especially if they are generated using one of these tools that just crawl your website to find all the pages. What information does this provide to search engines that they can’t find for themselves by crawling the site themselves? And If I do want to provide any more useful information, I have to edit the thing by hand, which I really can’t be bothered doing.

Friday, May 28, 2010

Google Maps elevation API

Version 3 of the Google Maps API adds a new dimension to maps, the elevation of locations. I did plan to knock together an example of how this works, but the example provided by Google is pretty good itself.

I think this new API could be very useful. The obvious use would be when planning a walk or a bike ride and wanting to get an idea for the terrain, but I’m sure there are many more uses.

Google Maps styled maps

Version 3 of the Google Maps API adds support for styled maps, which means it is now possible to hide features or display them differently on the base map. This could lead to some interesting customisations, and also leads to some questions about how exactly Google have implemented this? Are they creating map tiles on the fly? Or have they stored every possible permutation of styling? Surely the former, but how come it doesn’t seem to make any difference to the performance of rendering?

Anyway I had a play with the styled map wizard (which doesn’t work in IE for some reason) and thought I’d experiment with a map displaying just rail lines. And I kind of got this working, but there was a fundamental problem. Rail lines are only displayed when the map is zoomed in below a certain threshold so the map wasn’t any use to get a broad overview of rail lines in the UK for instance. And the styled maps API doesn’t seem to provide any control over this thresholding.

So it looks good as an initial implementation, but I think more control is required to be a completely useful API.

Tuesday, May 25, 2010

Google Maps geolocation API

Google have had an API to get the user’s location for a while (via the Google loader), but I didn’t think it was very good. But now there are two new methods of figuring out the user’s location, one using the new W3C standard and the other using Google Gears. The good news is they seem to do a very good job of locating the user (scarily so in fact), the bad news is that neither are supported by Internet Explorer or Safari.

Google Chrome uses Google Gears to do the job, FireFox uses the W3C method and frankly I’m not too bothered what Opera supports, since so few people use it.

One annoyance is the prompting you get in the browser, although this is fairly understandable since this information could potentially be used for evil. And it won’t be an especially useful feature until it’s adopted by more browsers. But a combination of all three methods may produce a reasonable compromise. 

Friday, May 21, 2010

Google Maps geocoding still sucks

I spent a little time today starting to convert some of my pages to the Google Maps API version 3. At the same time I thought I’d convert my geocoding to use the latest version since I was sure it would have improved since it was added to the API and failed to work too well three years ago. But no, it still sucks big time for postcode geocoding, as this test page demonstrates. But what is strange is that the GlocalSearch class still does a fine job of geocoding. Same company, different APIs and different results. This can’t be down to the Royal Mail throwing their toys out of the pram since the Ordnance Survey now provides geocoded postcode data for free so I assume it’s down to Google stuffing up.

Converting to Google Maps version 3

Google Maps API version 3 has just moved from the Google Labs to live so I thought I’d try converting a simple version 2 map to version 3. This is what the version 2 code looks like

  <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjtZCgAx5i04BiZDO6HlxhRQUdBDpWCOMRMbgTcqadX0jQ8HOERSxXxhk24TIBUpivovAKLrnpSio9w" type="text/javascript"></script>
  <script type="text/javascript">
    window.onload = function () {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(51.49506473014368, -0.130462646484375), 10);
        map.addControl(new GSmallMapControl());
        map.addControl(new GMapTypeControl());
        map.enableDoubleClickZoom();
        map.enableScrollWheelZoom();
        var layer = new GGeoXml("http://www.doogal.co.uk/LondonStationsKML.php");
        map.addOverlay(layer);
      }
    }
  </script>

And this is what the version 3 code looks like

  <script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>
  <script type="text/javascript">
    window.onload = function () {
      var latlng = new google.maps.LatLng(51.49506473014368, -0.130462646484375);
      var options = {
        zoom: 10,
        center: latlng,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };

      var map = new google.maps.Map(document.getElementById("map"), options);

      var georssLayer = new google.maps.KmlLayer('http://www.doogal.co.uk/LondonStationsKML.php');
      georssLayer.setMap(map);
    }
  </script>

The first thing I noticed is that the API key is no longer required which is very welcome, since it makes everything simpler (no more hassle when moving scripts between domains or trying to debug somewhere other than the domain or localhost). I guess the namespaces are a good thing and the class names seem more memorable. At this point I’m not sure if the API is any better than the old one, I’ll have to do some more digging before I can make a decision. But it does look like there’ll be quite a bit of work to convert old sites to the new API. Not that we’re forced to upgrade of course, at least for the moment.

Friday, April 23, 2010

JavaScript not executing in Internet Explorer 8

I’ve been caught out by this on numerous occasions and still haven’t learnt my lesson. So in an attempt to remember in the future and perhaps help somebody else out, here’s the situation. Take the following script declaration

<script type="text/javascript" src="Scripts/jquery-1.4.1.min.js" />

This may look valid and probably should be, but Internet Explorer thinks the script tag hasn’t been closed, which means any script blocks after it will be ignored. The declaration needs to be

<script type="text/javascript" src="Scripts/jquery-1.4.1.min.js"></script>

Sunday, April 11, 2010

Building a random postcode generator

Some time a go I noticed that the keywords ‘random postcode generator’ were landing quite a few people at my website, even though I didn’t have a random postcode generator. Which got me thinking there must be a need for such a tool, if people were ending up at a site that didn’t have one desperately looking for it. So I built one and it’s now the most visited page on my site.

So that’s my little success story. But then I started wondering why anybody would want a random postcode anyway. I guess some people are just after a fake postcode they can plug into a website registration form, for whatever reason, and that page will do the trick nicely. But I imagine there are other people who need to generate random postcodes programmatically for some reason, so I’m going to explain how my postcode generator works. It may not be the best approach, but it seems to work.

I guess one approach would be to have a complete list of UK postcodes and just select one at random. I didn’t have a complete list of postcodes and you probably won’t either, unless you’ve bought the PAF database from the Royal Mail. So I went for a more long winded approach that requires less initial data to get working. First I got together a table of postcode districts (original list and my more programmer friendly version), which is the first half of a postcode. Potentially you could try to create this part of the postcode randomly but you’d end up creating invalid postcodes a lot of the time. Also, not all postcode districts are of the same format.

So with this table in place, I get a random postcode district and then append a random second half of the postcode (which is always of the form digit-letter-letter). I did this in PHP using the following (this contains some code which is specific to my website but you should be able to modify for your own needs)

  $result = DbQuery("SELECT Postcode FROM PostcodeDistricts ORDER BY rand( ) LIMIT 1");
  $line = mysql_fetch_array($result, MYSQL_ASSOC);
  $num = rand(0,9);
  $firstChar = rand(1, 26);
  $secondChar = rand(1, 26);
  print($line["Postcode"] . " " . $num . chr(64+$firstChar) . chr(64+$secondChar));
  mysql_free_result($result);

The only problem with this is that it will sometimes generate invalid postcodes, so the next step is to check the postcode is valid. I did this using Google’s Local Search API from JavaScript, as so.

  <script type="text/javascript">
    /* <![CDATA[ */
    function CreateRandomPostcode()
    {
      document.getElementById("postcode").innerHTML = "Thinking";
      TryCreateRandomPostcode();
    }
 
    function TryCreateRandomPostcode()
    {
      document.getElementById("postcode").innerHTML += ".";
      var xmlhttp;
      if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest();     // Firefox, Safari, ...
      }
      else if (window.ActiveXObject)   // ActiveX version
      {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");  // Internet Explorer
      }
 
      xmlhttp.onreadystatechange = function() {
        if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
          Geocode(xmlhttp.responseText);
        }
      }
      xmlhttp.open("GET", "CreateRandomPostcode.php", true);
      xmlhttp.send();
    }
 
    function Geocode(postcode)
    {
      var localSearch = new GlocalSearch();
      localSearch.setSearchCompleteCallback(null,
        function()
        {
          if (localSearch.results[0])
          {
            var results = localSearch.results[0];
 
            if ((results.lat > 49) && (results.lat < 61) && (results.lng > -12) && (results.lng < 3)) {
              document.getElementById("postcode").innerHTML = "Your random postcode is " + postcode;
              document.getElementById("lat").value = results.lat;
              document.getElementById("long").value = results.lng;
            }
            else {
              TryCreateRandomPostcode();
            }
          }
          else
          {
            TryCreateRandomPostcode();
          }
        });
 
      localSearch.execute(postcode + ", UK");
    }
 
    /* ]]> */
  </script>

Basically it tries to geocode the postcode and if it fails (or it lies outside the UK), assumes the postcode is not valid and tries a different postcode until a valid one is produced. This could all be done server-side by hacking the Local Search API, but this met my simple needs.

Friday, April 02, 2010

Temporary file class for C#

Quite frequently I need to create a temporary file, do some processing on it, then delete it. In order to ensure the file gets deleted, I put the file deletion code in a try…finally, which got me thinking about writing a simple class that implements IDisposable to handle this scenario, allowing me to use using. It’s very simple, but here it is anyway.

  public class TemporaryFile : IDisposable
  {
    public TemporaryFile(string fileName)
    {
      this.fileName = fileName;
    }

    ~TemporaryFile()
    {
      DeleteFile();
    }

    public void Dispose()
    {
      DeleteFile();
      GC.SuppressFinalize(this);
    }

    private string fileName;
    public string FileName
    {
      get { return fileName;  }
    }

    private void DeleteFile()
    {
      if (File.Exists(fileName))
        File.Delete(fileName);
    }
  }

Saturday, March 27, 2010

jQuery zoom addin with image map support

In the past I fiddled around with the moozoom plugin to add support for image maps and on a new project I needed the same thing. But this new project uses a lot of jQuery stuff and jQuery and mootools don’t play together particularly well. Although there are a lot of image zooming plugins available for jQuery, none of them did exactly what I needed. I just wanted image zooming, panning and support for image maps. Anyway, this is what I came up with. This is my first adventure in the world of jQuery plugins, so it may not be a perfect implementation but it works for me…

Usage is pretty simple

$('.selector').zoomable();

where .selector is the selector for your image. View an example here (that page will also be the place I put all new versions of this code). You can now also programmatically zoom in and out using $('.selector').zoomable('zoomIn'); and $('.selector').zoomable('zoomOut'); which you can hook up to buttons for users without a mouse wheel

Here’s the code (requires jQuery and jQuery UI)

(function ($) {
  $.fn.zoomable = function (method) {
 
    return this.each(function (index, value) {
      // restore data, if there is any for this element
      var zoomData;
      if ($(this).data('zoomData') == null) {
        zoomData = {
          busy: false,
          x_fact: 1.2,
          currentZoom: 1,
          originalMap: null,
          currentX: 0,
          currentY: 0
        };
        $(this).data('zoomData', zoomData);
      }
      else
        zoomData = $(this).data('zoomData');
      
      var init = function() {
        if (value.useMap != "") {
          var tempOriginalMap = document.getElementById(value.useMap.substring(1));
          zoomData.originalMap = tempOriginalMap.cloneNode(true);
          // for IE6, we need to manually copy the areas' coords
          for (var i = 0; i < zoomData.originalMap.areas.length; i++)
            zoomData.originalMap.areas[i].coords = tempOriginalMap.areas[i].coords;
        }

        $(value).css('position', 'relative').css('left', '0').css('top', 0).css('margin', '0');

        $(value).draggable();

        // jquery mousewheel not working in FireFox for some reason
        if ($.browser.mozilla) {
          value.addEventListener('DOMMouseScroll', function (e) {
            e.preventDefault();
            zoomMouse(-e.detail);
          }, false);
          if (value.useMap != "") {
            $(value.useMap)[0].addEventListener('DOMMouseScroll', function (e) {
              e.preventDefault();
              zoomMouse(-e.detail);
            }, false);
          }
        }
        else {
          $(value).bind('mousewheel', function (e) {
            e.preventDefault();
            zoomMouse(e.wheelDelta);
          });
          if (value.useMap != "") {
            $(value.useMap).bind('mousewheel', function (e) {
              e.preventDefault();
              zoomMouse(e.wheelDelta);
            });
          }
        }

        $(value).bind('mousemove', function (e) {
          zoomData.currentX = e.pageX;
          zoomData.currentY = e.pageY;
        });
      };

      var left = function() {
        return parseInt($(value).css('left'));
      };
      
      var top = function() {
        return parseInt($(value).css('top'));
      }
      
      var zoomIn = function() {
        // zoom as if mouse is in centre of image
        var parent = $(value).parent()[0];
        zoom(zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomOut = function() {
        // zoom as if mouse is in centre of image
        var yi = parseInt($(value).css('top'));
        var parent = $(value).parent()[0];
        zoom(1 / zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomMouse = function (delta) {

        // zoom out ---------------
        if (delta < 0) {
          zoom(1 / zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }

        // zoom in -----------
        else if (delta > 0) {
          zoom(zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }
      };

      var zoomMap = function () {
        // resize image map
        var map = document.getElementById(value.useMap.substring(1));
        if (map != null) {
          for (var i = 0; i < map.areas.length; i++) {
            var area = map.areas[i];
            var originalArea = zoomData.originalMap.areas[i];
            var coords = originalArea.coords.split(',');
            for (var j = 0; j < coords.length; j++) {
              coords[j] = Math.round(coords[j] * zoomData.currentZoom);
            }
            var coordsString = "";
            for (var k = 0; k < coords.length; k++) {
              if (k > 0)
                coordsString += ",";
              coordsString += coords[k];
            }
            area.coords = coordsString;
          }
        }
      };

      var zoom = function (fact, mouseX, mouseY) {
        if (!zoomData.busy) {
          zoomData.busy = true;

          var xi = left();
          var yi = top();

          var new_h = (value.height * fact);
          var new_w = (value.width * fact);
          zoomData.currentZoom = zoomData.currentZoom * fact;

          // calculate new X and y based on mouse position
          var parent = $(value).parent()[0];
          mouseX = mouseX - parent.offsetLeft
          var newImageX = (mouseX - xi) * fact;
          xi = mouseX - newImageX;

          mouseY = mouseY - parent.offsetTop
          var newImageY = (mouseY - yi) * fact;
          yi = mouseY - newImageY;

          $(value).animate({
            left: xi,
            top: yi,
            height: new_h,
            width: new_w
          }, 100, function () {
            zoomData.busy = false;
          });

          zoomMap();
        }
      };
      
      if (method == "zoomIn")
        zoomIn();
      else if (method == "zoomOut")
        zoomOut();
      else
        init();
    });
  };
})(jQuery);

Tuesday, March 23, 2010

jqGrid hints and tips

I’ve been spending a lot of time with jqGrid recently. It’s a marvellous piece of work but the documentation doesn’t always match up to the quality of the code. I’m not complaining, I realise it’s free, it’s flipping great and as a developer myself I know documentation is always the last thing I want to tackle. But here are a few things I’ve discovered along the way where it wasn’t immediately apparent what was causing the problem.

Multiple rows being selected – This can be caused by a couple of things. The first thing to check is that the row IDs being used are sensible. My problem was caused by having some rows with the same ID. Another time the problem cropped up when I used email addresses as my row IDs. It seems the grid has problems with certain characters being used in IDs. The @ symbol did it for me but there are others, like spaces, that can cause problems.

Arbitrary XML – The docs say that jqGrid can accept any arbitrary XML document, so long as you provide a mapping to describe how to map it to the standard XML format. This is almost true, but falls over if your XML document contains attributes that you want to pull into the grid. Attributes aren’t supported at all. I managed to get round this in a couple of steps. First I had to hack jqGrid so the loadComplete event was triggered before the grid was actually populated, rather than after. Then I could fiddle with my returned XML before jqGrid tried to read it. Then I wrote some code to convert attributes to elements with he same name (this could probably be improved with some jQuery magic but it did the job for me) and called it for the relevant attributes.

function setNodeText(newElem, text) {
  if (text != null) {
    try {
      newElem.textContent = text;
    }
    catch (e) {
      newElem.text = text;
    }
  }
}

function convertAttributeToElement(xml, node, attributeName) {
  var name = node.getAttribute(attributeName);
  addChildNode(xml, node, attributeName, name);
}

function addChildNode(xml, parent, nodeName, value) {
  var newElem = xml.createElement(nodeName);
  setNodeText(newElem, value);
  parent.appendChild(newElem);
}

Word wrap – I wish this was on by default, since it is now the first thing I do when I’m setting up a grid for the first time. Just modify the ui.jqgrid.css file so the definition for .ui-jqgrid tr.jqgrow td includes the following white-space: normal;

Sunday, March 14, 2010

Forcing a .NET application to run as 32-bit under 64-bit Windows

I was having problems with running a .NET application under 64-bit Windows 7. This was because by default, .NET applications are built to target any platform. This app clearly hadn’t been tested on 64-bit Windows so blew up when it tried to instantiate a 32-bit COM object. The following link explains some of the options on how to fix this problem.  

http://www.lostechies.com/blogs/gabrielschenker/archive/2009/10/21/force-net-application-to-run-in-32bit-process-on-64bit-os.aspx

Obviously setting the platform target wasn’t an option for me since I didn’t have access to the source code. Using corflags.exe also wasn’t possible because the application was strong named so setting that flag would break the strong naming. So I was forced to create a wrapper application that was compiled for 32-bits. This initially also didn’t work but the only thing I needed to do was add the STAThread attribute to my wrapper’s Main method since the application I was calling also had this attribute on its Main method. Then it all worked, hurrah!

Now I just need to figure out how to do the same thing for a .NET service… And for all you .NET developers out there, if you don’t have the time or resources to test on 64-bit Windows, just flip that platform target switch to x86. Even the cheapest new PCs come with 64-bit versions of Windows (my laptop cost £400) and flipping that switch pretty much guarantees your app will work on 64-bit platforms.

Friday, March 12, 2010

XML encoding in PHP

I’ve needed it in JavaScript in the past and now I need it in PHP. Escape all those pesky special XML characters like so

  function XmlEncode($content)
  {
    $trans = array("&" => "&amp;", "<" => "&lt;", ">" => "&gt;", "'" => "&apos;",
      "\"" => "&quot;", "’" => "&apos;");
  	return strtr($content, $trans);
  }

Friday, March 05, 2010

Collatz conjecture implementation

I only learnt about the Collatz conjecture today so thought I'd implement it in JavaScript. Type in a starting number and watch as the process converges to 1, probably

Monday, February 15, 2010

Hollands Pies reviewed

So I’m slowly working my way through the pies I purchased the other day and he’s what I think of them so far.

Meat pie – this is a little pie, about the size of a standard pork pie but heated up. It contains pork and beef and tastes pretty yummy. My only complaint is the scalding hot liquid contained within which burnt my mouth first time, although this was easily rectified second time around by slicing the pie in half.

Cheese and onion – many years ago when I was a vegetarian, this was one of the few pies I was able to partake of. That and the butter pie, which seems to have disappeared from the face of the earth, probably due to its unfortunate name. It did contain more than just butter, which is why I didn’t collapse from heart failure. But back to the cheese and onion pie. Unfortunately this was disappointing, there was very little evidence of onion, just some bright yellow gloop that may well have originated at the Sellafield reprocessing plant (I joke of course, before anyone tries to sue me).

Potato and meat – before some government organisation got involved, this was known as a meat and potato pie. Now it’s just a perfectly decent pie with a silly name.

Steak and kidney pudding – this is where things get interesting. This is not like any other pie you’ll ever experience. With most pies the pastry is essentially just some hard packaging to keep the contents together, with little merit of its own. But with the steak and kidney pudding, the suet case is an essential part of the whole experience. Not only are the contents tasty, but so is the pastry. Even the cooking instructions are unique. No oven for this pie, but a pan of boiling water. There is one downside to this. Whereas pies are generally a pretty portable food stuff, the steak and kidney pudding needs a plate and fork.

So in conclusion, I would recommend all of these pies from Hollands, except the cheese and onion. But for ultimate pleasure, order ten steak and kidney puddings…

Saturday, February 13, 2010

FreeFlow now open source

I don’t really have time to maintain it anymore, so I’ve open sourced FreeFlow, my little toolkit and administration tool for Metastorm BPM. Go grab it from CodePlex and make it better!

Wednesday, February 10, 2010

Using PUT and DELETE in WCF web services

IIS7 handler mappingsI’ve been doing a little work on a web page that needs to grab some data from a web service and then update that data via PUT and DELETE requests. I haven’t got access to the web service currently so decided to write my own dummy implementation using WCF.

The first problem I encountered was that by default PUT and DELETE calls won’t be allowed at all by IIS. This can be resolved by fiddling with the Handler Mappings options in IIS7 to allow the required verbs, as shown on the left.

My next problem was that I wanted to use the same URL for both PUT and DELETE requests. The WebInvoke attribute that is applied to your WCF method doesn’t support multiple HTTP verbs, so I was a little stumped at this point. The solution is actually pretty straightforward, have multiple methods pointing to the same URL, like so

[WebInvoke(Method = "DELETE", UriTemplate = "EditFolder")]
public void DeleteFolder(Stream input)
This is actually probably a good restriction by WCF. If you want different verb types on the same URL, the chances are that you have two different operations anyway.

Wednesday, February 03, 2010

Unminifying Javascript files

Am I a web developer? I dunnow, I’ve spent a lot of time building web sites but I don’t really feel like a web developer. The web just plain scares me. Debug any web application and you’re faced with shedloads of anonymous functions, eval code and general weirdness. Look at the poster boys of Web 2.0, JQuery and its children JQuery UI and JQGrid. Yes they are fecking marvellous bits of technology but try stepping through the code and tell me you have the faintest idea what on earth is going on. It feels like the web is held together with a pile of JavaScript sellotape.

But in some kind of sado-masochistic twist, the web is now even more convoluted because the JavaScript is even less readable because it’s all been minified in an attempt to save bandwidth. So now if I find myself in a debugger trying to figure out what some JavaScript is doing, I see one long line of code with useless function names and I go off to bang my head against a wall because it’s less painful. But I have found one tool that improves the situation, JS Beautifier, which will attempt to make your minified code more readable and understandable by a human. Beyond the initial rant, this is primarily a reminder to myself.