Monday, June 30, 2014

QR Codes in Handlebars

User’s don’t like to type in long URLs, and if they are on mobile they don’t like to type at all. Here is a simple Handlebars helper to make QR codes. But first, here is how easy it is to use:
  {{{qrcode "http://darrendev.blogspot.com/"}}}
Why would you want a QR code in a web site, when you could just as well put a clickable link? One example is to move a URL from viewing it on a desktop to your mobile. Another example is for ticketing: you put a booking code in the URL, make a QR code from it, and the venue will scan it (and the URL takes them directly to the validation function of their web app). No typing. Quick, safe and easy for everyone involved.
Handlebars is a JavaScript templating engine, easy to use and understand, and it allows expanding it with your own plugins, which thankfully are equally easy to write. Here is the above plugin, in full:
Handlebars.registerHelper("qrcode", function(data) {
var qrcode  = new QRCode(-1,QRErrorCorrectLevel.H);
qrcode.addData(data);
qrcode.make();

var bg="#fff",fg="#000";
var n=qrcode.getModuleCount();
var tilesize = 4;
var sz=n*tilesize;

var s='<table style="width:'+sz+'px;height:'+sz+
  'px;border:0;border-collapse:collapse;background:'+
  bg+'>';
for(var row = 0; row < n; row++ ){
    s+='<tr style="height:'+tilesize+'px">';
    for(var col = 0; col < n; col++ ){
        s+='<td style="background:'+
          (qrcode.isDark(row, col) ? fg:bg)+
          ';width:'+tilesize+'px"></td>';
        }
    s+='</tr>';
    }
return s;
});
To use this plugin you need to fetch an open-source JavaScript library unzip it and include it on your site with:
<script type="text/javascript" src="qrcode.min.js">
Aside: the core of the above plugin was taken from one of the examples that come with the library. I chose to use tables, rather than canvas, as it gives much wider browser support, with no disadvantage.
Written with StackEdit.

Wednesday, June 25, 2014

Casper, d3, jquery and clicking

I spent (wasted? invested?) an awful lot of time yesterday on trying to use CasperJS to click a link inside an SVG diagram that had been made by d3.js. I’ll start this article by showing my Foolish Mistake, but then will document what I did learn.

Foolishness

Normally, in CasperJS, we write this.click('#myButton'), where “#myButton” can be just about any CSS selector.
Aside: it is normally this.click() rather than casper.click()
because it is normally in the handler function of a casper.waitXXX()
function.
This wasn’t working for me trying to click an SVG <g> tag, that starts a search running. I was taking a screenshot 0.5s later to check it had worked, and it showed the page had not changed.
And that turned out to be foolish. The click was working, the search was working, d3 and SVG had nothing to do with anything. The problem was simply were no search results found, and so it never moved to the next page. When I changed the search string, suddenly click() was working.
So my troubleshooting turned out to be barking up the wrong tree. But, still, I did learn a few things from it.

Using evaluate() With jQuery

Instead of calling this.click() you can do something like this:
this.evaluate(function(){
  $('#myButton').click();
  };
This runs JavaScript from within the browser’s context. In this case I use jQuery. This works just as well as using Casper’s click() outside the evaluate().
Here there is no advantage. But, by being in a different scope, we have extra flexibility: we could call other functions, or add new event handlers, etc, etc.

Using evaluate() With d3

Here was another of my attempts, but this one does not work:
this.evaluate(function(){
  d3.select('#myButton').click();
  };
The reason: a d3 selection does not have a ready-made click() function.

Making events happen

this.evaluate(function(){
  var evt = document.createEvent("MouseEvents");
  evt.initMouseEvent("click", true, true, window,
    0, 0, 0, 0, 0, false, false, false, false, 0, null);
  return d3.select('#myButton').node().dispatchEvent(evt);
  };
This is how you do a click with d3 (you could use this approach with jQuery too (see a helper function), but are unlikely to ever need to). The first lines create a (simulated) mouse click. The final line sends that event to the DOM item of interest.
Aside: dispatchEvent() returns false if any of the event handlers
cancelled it, true otherwise.
I learned this here; that answer also says this should have worked:
this.evaluate(function(){
  d3.select('#myButton').on("click")()
  };
This definitely does not work for me. Why? It is actually a cheat, trying to find and call the click handler for the button. In my case the click handler was attached to the parent object (a <g>) not the object I was calling select on. (Also, because it is a cheat, this approach does not work when you’ve attached multiple handlers.)
By the way, if not using jQuery or d3, you can use querySelector() and do it this way:
this.evaluate(function(){
  var evt = document.createEvent("MouseEvents");
  evt.initMouseEvent("click", true, true, window,
    0, 0, 0, 0, 0, false, false, false, false, 0, null);
  return document.querySelector('#myButton').dispatchEvent(evt);
  };

Summary

The real lesson here, for me, was when something doesn’t work, make sure you are judging success in the right way!
Beyond that, it turns out there are a whole host of ways to click a button in a CasperJS script. Use the simplest when you can, bear the others in mind for special occasions.
Written with StackEdit.

Friday, June 20, 2014

Captcha never changed (securimage)

It took a while of testing before I noticed, then I finally realized the captcha on a web app I was working never changed!

Thankfully, before launching into some heavy-duty troubleshooting, I had a flash of inspiration: to make my unit tests work nicely I was setting the random seed to a constant (i.e. to get repeatable test data). That test data is actually being generated for every page request of the main web app (just during development).

So the fix was as trivial as calling mt_srand(time());  just before creating the Captcha  (as I'm using the Securimage library, just before the $img = new Securimage(); call).

If only all troubleshooting was this quick!