Wednesday, November 23, 2011

[js] IE yellow download bar

Say you have a "download" link. Clicking it kicks off a Javascript function that ultimately navigates the browser to your download PHP script, which serves up the file via something like:

/* download.php */
..
header('Cache-Control: public'); 
header("Content-type: application/pdf");
header('Content-Disposition: attachment; filename="' . $filename . '"');
readfile($path . $filename);

In IE, this usually pops up a "File Download" dialog asking if you want to open or save the file; pretty convenient. But something went awry somewhere. You're now getting a yellow bar warning instead. Why?

To help protect your security, Internet Explorer blocked this site from downloading files to your computer. Click here for options...

Check your Javascript. IE will allow you, the developer, to initiate a download from a click event as long as you do so within the same execution thread. Let the click thread end, though, and try to navigate to the download script from another thread, and, hello, yellow bar. In other words, look out for setTimeout.

  download_onclick:function(id) {
    window.location.href = 'download.php?id=' + id;    // good to go
  }, ..
  download_onclick:function(id) {
    setTimeout(function() {
      window.location.href = 'download.php?id=' + id;  // yellow bar
    }, 1);
  }, ..

Monday, November 21, 2011

[php] Content-type:image whitespace woes

So today I noticed the code for displaying scanned images was no longer working. This code basically boils down to:

header('Content-type: image/jpeg');
readfile('some_file.jpg');

Pretty simple, right? Yet somehow, even though some_file.jpg really does exist, and it really is a JPEG, I'm getting nothing. IE gives me the "image not found" red X icon, Chrome gives me a blank. What?

Rummaging around the web, I found a couple references to how unintentional whitespace output by your PHP script can screw your header() call into the ground. This seemed a likely explanation, but where (and why) is my PHP spitting out unwanted whitespace?

Turns out the culprit was lurking within these closing lines of an include:

  static function asOptionalJoin($fk = 'ipc') {
    $c = new static();
    return CriteriaJoin::optional($c, $fk);
  }
}
?> 

Do you see it? Trick question; it's invisible.

  static function asOptionalJoin($fk = 'ipc') {
    $c = new static();
    return CriteriaJoin::optional($c, $fk);
  }
}
?> <— look again

Yes, a space immediately to the right of the closing ?> PHP tag was the source of my grief. Take that space away, and the header call works again and the image springs back to life.

Interesting side note: one of the aforementioned resources explains that closing ?> tags are not only optional (didn't know this), but that some frameworks actually forbid them in PHP-only source files for the very reason of keeping accidental whitespace out of the output.

Sounds good to me; they're gone.

Thursday, November 3, 2011

[js] Another unexpected result

Sticking with the same theme as last post's short-circuit trickery, another Javascript operation that has an unexpected result (for me, anyway) is the assignment operation.

alert (x = 'hi');

The resultant friendly greeting demonstrates that the assignment operation not only assigns a value but actually evaluates as that value. This offers additional opportunities at simplification:

add(last_row = row);
return this.property = object.calculate(arg);
// etc.

Wednesday, November 2, 2011

[js] Short-circuit trickery

Your function has a string argument, arg, that may or may not be supplied from the caller. You want to assign result with the value of arg, if it exists, or the string 'none', if it doesn't.

Quick, how do you do this?

var result;
if (arg)
  result = arg;
else
  result = 'none';

One point.

var result = arg ? arg : 'none';

Two points.

var result = arg || 'none';

Three points for style! This kind of operation is formally known as null coalescing, and for the longest time I was unaware that Javascript had it.

Hold on, how does this even work? You're using the short-circuit OR operator. Isn't the result of a logical operation either true or false?

You know, that's a good question. I had always assumed so, and you'll even find Javascript resources on the web stating the same. But it isn't so. In Javascript, the result of short-circuit OR will be the actual value of the first operand that evaluates as true (if one of them does, that is—if not, you'll get back the value of the last operand.)

result = 4 || 'fred';     // result = 4
result = true || 'fred';  // result = true
result = 0 || 'fred';     // result = 'fred' because 0 evaluates as false
result = false || 0;      // result = 0

Does the short-circuit AND also result in an actual value? Why, yes, it does. If all operands evaluate as true, you'll get the last value; otherwise, you'll get the value of the first operand evaluated as false. And this lends itself to another nice trick.

name = rec && rec.formatName();

This is equivalent to the slightly clumsier:

if (rec)
  name = rec.formatName();

Tuesday, November 1, 2011

[php] Standard get() and getr()

Would you prefer $myarray['apple'] to return null when that index doesn't exist, rather than Notice: Undefined index: apple? How about $myobj->banana to return null rather than Notice: Undefined property: stdClass::$banana? Here's a simple getter to accommodate these fervent desires.

function get($e, $id, $default = null) {
  if (is_array($e))
    return isset($e[$id]) ? $e[$id] : $default;
  else if (is_object($e))
    return isset($e->$id) ? $e->$id : $default;
  else 
    return $default;
}
$result = get($myarray, 'apple');  // instead of $myarray['apple']
$result = get($myobj, 'banana');   // instead of $myobj->banana

Another useful getter is getr, which can navigate recursively through nested object/array hierarchies (and bail out gracefully at any point it runs into an undefined.)

function getr($e, $prop, $default = null) {
  if (strpos($prop, '.') !== false) {
    $props = explode('.', $prop, 2);
    $e0 = get($e, $props[0]);
    if ($e0 === null)
      return $default;
    else 
      return getr($e0, $props[1], $default);
  } else {
    return get($e, $prop, $default);
  }
}
$myrec1->name = array('first'=>'Joe','last'=>'Blow');
$myrec2->name = null;
echo getr($myrec1, 'name.last');  // returns 'Blow'
echo getr($myrec2, 'name.last');  // returns null