Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

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);
  }, ..

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();

Monday, October 31, 2011

[js] Currying functions

The concept of currying a function is not inherent to Javascript but is easy to provide and useful. It allows you to preset the value of one (or more) of a function's argument list, so that when the function is called later it "remembers" what had been preset.

Consider the common scenario of building a record table, where each row contains an anchor for editing/selecting that row's record. Currying allows you, in effect, to bind a record to its anchor's onclick event:

rec = {'id':12,'name':'George'};
anchor = document.getElementById('some_anchor');
anchor.onclick = some_function.curry(rec);

function some_function(rec) {
  alert(rec.name);
}

A little trumped up for demonstration purposes, as in reality we'd be iterating through records and creating (not referencing) anchors, but the important part's the same. We can't point the anchor's onclick directly to some_function, because that function expects a rec argument which, of course, a fired onclick won't provide. So we invoke the target function's curry method to preset that argument for when it's called later (e.g. when the anchor is clicked).

Again, curry isn't native Javascript, but is part of any decent framework and otherwise easy to implement:

Function.prototype.curry = function() {
  var fn = this;
  var args = Array.prototype.slice.call(arguments);
  return function() {
    return fn.apply(fn, args.concat(Array.prototype.slice.call(arguments)));
  }
}

Technically what happens when you invoke a function's curry method is that you get a reference to a new function that wraps the original. When the new function is later invoked, the inner (original) function is called with the arguments that had been provided to curry, which are still in scope by virtue of the closure formed when the new function was created, plus any arguments now provided to the new function.

Sunday, October 30, 2011

[js] Forwarding arguments

If you want to call another function with all arguments supplied to this function, use the function's apply method:

pop:function() {
  this.onpop.apply(this, arguments);
  // stuff
}

Saturday, October 29, 2011

[js] IE input select() bug

Do a select() focus() on an input box with contents. Now click on another control (e.g. checkbox). The input box contents remain "selected" even though the focus is now on the other control.

To fix, I had to assign an onblur handler to the input box:

input.onblur = function() {
  var value = this.value;
  this.value = '';
  this.value = value;
}

Pretty stupid.