Tuesday, November 19, 2013

input type=numeric fix for Android 4.x

Gosh, RWD (responsive web design) combined with targeting both desktop, iOS and Android browsers is hard work. I just thought I was there when I got my  <input type="numeric" />  box working in all of Firefox, Chrome, Opera and Android 2.3, to my satisfaction. (Not identical behaviour: the up/down buttons only appear in Chrome and Opera, but that is the principle of progressive enhancement, and good enough.) I had to add a hack for Android 2.3, which makes things one pixel out of alignment in the desktop browsers at 400px or less. I can live with that, assuming desktop users won't be at 400px or less.

It worked in iOS7 (iPhone). Yeah and Phew!

On Android 4.2, native browser, there is this gap on the right of the input boxes!?!? It is just wide enough for the up/down buttons, but they are not drawn. Grrr....
(Some googling found someone on 4.0 with the same problem; other than that it seems almost unknown!)

Let me cut straight to the fix:

@media (max-width: 800px) {
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
}

All of that just came to me in a dream, and it worked first time. (No, actually I got that from this StackOverflow answer )

The media query is crude, as it means desktop Chrome won't show the buttons if you make the browser narrower than that, whereas a tablet with higher screen resolution will still show the gap. So, I'll probably switch to using user-agent detection later on.

Tuesday, November 12, 2013

Just discovered a new way to loop through months, up to the current month. I used to have horrible code like this:

for($y=2009;$y<=date("Y");++$y){
  for($m=1;$m<=12;++$m){
    if($y==date("Y") && $m>=date("m"))continue;
    $t = strtotime("$Y-$m-01");
    echo date("M_Y",$t)."\n";
    }
  }


If wanting days, you get $t then do $days_in_month = date("t",$t);

Here is my new solution:

$start = strtotime("2009-01-15");
$now = time();
$secs_per_month=(int)((365.25*86400)/12);
for($t=$start;$t<$now;$t+=$secs_per_month){
    echo date("M_Y",$t)."\n";
    }


I.e. choose a day in the middle of the month, and do everything in seconds. Fewer calls to date() and shorter code. Obvious in hindsight!



Thursday, November 7, 2013

Saving downloaded files in SlimerJS (and Casper and Phantom)

It seems a common request is to be able to see not just the HTML of the main page that PhantomJS/SlimerJS are downloading, but also all the other files (images, CSS, JavaScript, fonts, etc.) that are being fetched. You can use onResourceReceived to see them being fetched, but not their body.

The situation with PhantomJS is a bit confusing: I believe there is a patch to allow this, but it hasn't been applied yet. There is also a download API being proposed (or possibly already implemented), but that appears to be for the special case of files that have a Content-Disposition: attachment header. (?)

In SlimerJS it is possible to use response.body inside the onResourceReceived handler. However to prevent using too much memory it does not get anything by default. You have to first set page.captureContent to say what you want. You assign an array of regexes to page.captureContent to say which files to receive. The regex is applied to the mime-type. In the example code below I use /.*/ to mean "get everything". Using [/^image/.+$/] should just get images, etc.

The below code sample will download and save all files. It is complete; you just have to edit the url at the top.

var url="http://...";

var fs=require('fs');
var page = require('webpage').create();

fs.makeTree('contents');

page.captureContent = [ /.*/ ];

page.onResourceReceived = function(response) {
//console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response));
if(response.stage!="end" || !response.bodySize)return;

var matches = response.url.match(/[/]([^/]+)$/);
var fname = "contents/"+matches[1];

console.log("Saving "+response.bodySize+" bytes to "+fname);
fs.write(fname,response.body);
};

page.onResourceRequested = function(requestData, networkRequest) {
//console.log('Request (#' + requestData.id + '): ' + JSON.stringify(requestData));
};

page.open(url,function(){
    phantom.exit();
    });


It is verbose in that it says what it is saving. If you want it much more verbose, to see what other information is passing back and forth, there are two logging lines commented out.

WARNING: this works in SlimerJS 0.9 (and should work in 0.8.x), but the API may change in future (to keep in sync with PhantomJS).