Posts Tagged: JavaScript


17
May 08

PHP’s create_function() and closures

A coworker recently asked me what the difference was, functionally, between PHP’s create_function() function and traditional closures that you might find in languages with first-class functions, like Ruby or JavaScript. You can pretty easily illustrate this with a couple of examples.

First, a bit about closures. The idea with closures is that you can cleanly and readably pass around a bit of logic as an object, and any references that that object makes to variables in the surrounding scope must persist until that object is done with them.

So here’s an example in JavaScript:

function getGreeter(name) {
  return function(salutation) {
    alert(salutation + ', ' + name);
  };
}

var greeter = getGreeter('Eddy');
greeter('Hello');   // Hello, Eddy
greeter('Howdy');   // Howdy, Eddy
greeter('Bonjour'); // Bonjour, Eddy

Here’s the closest equivalent in PHP:

$code = '$name, $salutation', 'print $salutation . ', ' . $name;';
$greeter = create_function($code);
$greeter('Eddy', 'Hello');
// etc.

And that’s a callback, not a closure. In JavaScript the garbage collector reclaims the memory used by the anonymous “greeter” function… but in PHP, functions get declared and stay declared, so every time you call create_function(), you increase the memory usage.

It gets worse. This is basically what PHP does internally:

function create_function($args, $code) {
    // create a random $functionName
    eval('function ' . $functionName . '($args){$code}');
    return $functionName;
}

Yeah, the entire thing is evaluated. So not only does it not get garbage collected, but it has all the traditional problems of eval()—it’s slow, difficult to debug, and uncacheable by bytecode caches like APC. Problems that closures don’t have in other languages.

It’s why you can do something like this (which works on the same principle as SQL injection)…

$code = 'print "I print repeatedly.\n"; } print "I print once.\n"; if (false) {';
$function = create_function('', $code);
call_user_func($function);
call_user_func($function);
call_user_func($function);

// I print once.
// I print repeatedly.
// I print repeatedly.
// I print repeatedly.

…and why you should never use create_function().

Like this post? You might also like Coalmine, my centralized error tracking service for your apps. Coalmine captures errors and all kinds of helpful debugging information, notifies you, and makes it all searchable. Check it out!

26
Jun 07

Detecting plugins in Internet Explorer (and a few hints for all the others)

There’s a lot of bad information out there about detecting plugins in Internet Explorer. I know; I spent days crawling through it all in order to create a plugin detection tool for a client. It’s not that the code you’ll find in the crevices of those forgotten pages doesn’t work. It does. The problem is that virtually all of it is grossly inefficient and almost immediately outdated because it’s version-based and has to be updated with each new plugin release. On top of that, some of it still uses VBScript, or even worse, JavaScript that writes VBScript. Since plugin detection itself continues to be necessary in some unique situations, I was determined to find a way to do it that didn’t require constant maintenance.

The problem is that no one quite knows what to look for when dealing with Internet Explorer’s plugins. Plugins in IE are ActiveX-based, so there’s no single API for them all—each has its own method of returning, for example, version information. What this basically means is that while Firefox and other browsers are putting all of their plugin information in one handy place, Internet Explorer is jealously guarding its plugin-related secrets. It’s like the Dick Cheney of browsers. And we are, um, the Information Security Oversight Office in that analogy.

Er… let’s just jump right in with some working detection code.

Adobe Reader

Detecting Adobe Reader (formerly Acrobat Reader) is pretty straightforward, except for the hitch that the ActiveX control was renamed in version 7. The key for version detection here is GetVersions(). Unfortunately, no version information is available for Adobe Reader 8 and later for non-IE browsers.

var isInstalled = false;
var version = null;
if (window.ActiveXObject) {
  var control = null;
  try {
    // AcroPDF.PDF is used by version 7 and later
    control = new ActiveXObject('AcroPDF.PDF');
  } catch (e) {
    // Do nothing
  }
  if (!control) {
    try {
      // PDF.PdfCtrl is used by version 6 and earlier
      control = new ActiveXObject('PDF.PdfCtrl');
    } catch (e) {
      return;
    }
  }
  if (control) {
    isInstalled = true;
    version = control.GetVersions().split(',');
    version = version[0].split('=');
    version = parseFloat(version[1]);
  }
} else {
  // Check navigator.plugins for "Adobe Acrobat" or "Adobe PDF Plug-in"*
}

* Nicole Lucas adds that “Adobe Acrobat” won’t work with Adobe Reader 8 because Adobe changed the name to “Adobe PDF Plug-In for Firefox and Netscape”. Thanks, Nicole.

Flash Player

Flash is even easier. The version method in this case is GetVariable('$version').

var isInstalled = false;
var version = null;
if (window.ActiveXObject) {
  var control = null;
  try {
    control = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
  } catch (e) {
    return;
  }
  if (control) {
    isInstalled = true;
    version = control.GetVariable('$version').substring(4);
    version = version.split(',');
    version = parseFloat(version[0] + '.' + version[1]);
  }
} else {
  // Check navigator.plugins for "Shockwave Flash"
}

Java Runtime Environment

The JRE (formerly Java Virtual Machine, or JVM) is actually more difficult to handle than you would think. Determining if Java is installed is easy—a quick call to navigator.javaEnabled() returns a simple Boolean.* The problem is detecting the version and provider (Microsoft or Sun).

I never found a satisfactory solution to this. The gist is this: to get this information, you must load a Java applet. To load the Java applet, you must do it externally. But when an applet is loaded externally, it doesn’t load right away, so any programmatic calls to the exposed class methods must be delayed. And dealing with this with setTimeout() is awkward.

Nevertheless, here’s what I ended up with:

var isInstalled = navigator.javaEnabled();
if (!isInstalled) {
  return;
}

// Get version
var version = null;
if (/*@cc_on ! @*/ false) { // This JRE check does not depend on ActiveX, only IE
  // IE requires the 'classid' attribute upon object creation.
  var element = '<object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93">';
  var applet  = document.createElement(element);
  applet.id = 'applet';

  // IE requires that objects be displayed in order to interact with them
  applet.style.width  = '1px';
  applet.style.height = '1px';

  // Create and append the object's 'code' parameter
  var param = document.createElement('param');
  param.name  = 'code';
  param.value = 'JavaDetector.class';
  applet.appendChild(param);

  // Append the applet to <body>
  document.body.appendChild(applet);

  // Attempt to get the version
  var window.java = new Object();

  // Synchronous calls to getVendor(), etc. result in an exception, but despite
  // the timeout of 0 this call will not execute immediately
  setTimeout(function() {
    try {
      var applet = document.getElementById('applet');
      window.java.vendor  = applet.getVendor();
      window.java.version = parseFloat(applet.getVersion());
    } catch (e) {
      // Do nothing
    }
  }, 0);
} else {
  if (typeof java != 'undefined') {
    version = java.lang.System.getProperty('java.version');
  } else {
    // Look for "Java Plug-in", "Java (version) Plug-in", and "Java Plug-in for Cocoa"
  }
  version = parseFloat(version);
}

And your Java class looks something like this:

public class JavaDetector extends java.applet.Applet {
    public static String getVendor() {
        String vendor = null;
        try {
            vendor = System.getProperty("java.vendor");
        } catch (Exception e) {
            vendor = "Microsoft Corp.";
        }
        return vendor;
    }

    public static String getVersion() {
        return System.getProperty("java.version");
    }
}

* Eric Gerds adds that “navigator.javaEnabled() does not reveal whether Java is installed or not in IE.” Apparently disabling Java in IE merely disables the applet tag. Applets can still be loaded with the object tag despite javaEnabled() returning false. As such, you may want to modify this code. See his complete comment below.

QuickTime Player

QuickTime is another one that poses difficulty for IE. Detecting it is simple enough: just check for QuickTime.QuickTime. But detecting the version poses other problems. The ActiveX control that returns version information for QuickTime must be enabled manually in Internet Explorer 7. When called, the user is prompted. Although it saves that information for all future visits to that page, the code that follows will typically return null for the version number the first time around. Unfortunately, I’ve found no good way to reliably detect the version through ActiveX alone.

The version number that you do receive isn’t immediately useful. The QuickTimeVersion property is in hexadecimal (for whatever reason), so you’ll need to convert it by calling QuickTimeVersion.toString(16) and then manually assembling the number.

var isInstalled = false;
var version = null;
if (window.ActiveXObject) {
  var control = null;
  try {
    control = new ActiveXObject('QuickTime.QuickTime');
  } catch (e) {
    // Do nothing
  }
  if (control) {
    // In case QuickTimeCheckObject.QuickTimeCheck does not exist
    isInstalled = true;
  }

  try {
    // This generates a user prompt in Internet Explorer 7
    control = new ActiveXObject('QuickTimeCheckObject.QuickTimeCheck');
  } catch (e) {
    return;
  }
  if (control) {
    // In case QuickTime.QuickTime does not exist
    isInstalled = true;

    // Get version
    version = control.QuickTimeVersion.toString(16); // Convert to hex
    version = version.substring(0, 1) + '.' + version.substring(1, 3);
    version = parseFloat(version);
  }
} else {
  // Check navigator.plugins for "QuickTime Plug-in"
}

RealPlayer

Because it’s had so many names (five, that I can count), RealPlayer is still one of the more inefficient plugins to check for. Thankfully, they’ve kept the API the same, however, so checking for the version is still just GetVersionInfo().

var isInstalled = false;
var version = null;
if (window.ActiveXObject) {
  var definedControls = [
    'rmocx.RealPlayer G2 Control',
    'rmocx.RealPlayer G2 Control.1',
    'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)',
    'RealVideo.RealVideo(tm) ActiveX Control (32-bit)',
    'RealPlayer'
  ];

  var control = null;
  for (var i = 0; i < definedControls.length; i++) {
    try {
      control = new ActiveXObject(definedControls[i]);
    } catch (e) {
      continue;
    }
    if (control) {
      break;
    }
  }
  if (control) {
    isInstalled = true;
    version = control.GetVersionInfo();
    version = parseFloat(version);
  }
} else {
  // Check navigator.plugins for "RealPlayer" and "RealPlayer Version"
}

Shockwave Player

Thankfully, Macromedia always made an effort to remain consistent with their naming. Thus, one simple call can detect any version of Shockwave. The version check here is ShockwaveVersion('')—yes, you must pass it an empty string.

var isInstalled = false;
var version = null;
if (window.ActiveXObject) {
  var control = null;
  try {
    control = new ActiveXObject('SWCtl.SWCtl');
  } catch (e) {
    return;
  }
  if (control) {
    isInstalled = true;
    version = control.ShockwaveVersion('').split('r');
    version = parseFloat(version[0]);
  }
} else {
  // Check navigator.plugins for "Shockwave for Director"
}

Windows Media Player

Windows Media Player is probably the easiest one to detect of them all, but also the most unreliable on non-Internet Explorer browsers. Firefox users will typically be missed here unless they’ve installed the WMP for Firefox plugin. The relevant version property is versionInfo.

var isInstalled = false;
var version = null;
if (window.ActiveXObject) {
  var control = null;
  try {
    control = new ActiveXObject('WMPlayer.OCX');
  } catch (e) {
    return;
  }
  if (control) {
    isInstalled = true;
    version = parseFloat(control.versionInfo);
  }
} else {
  // Check navigator.plugins for "Windows Media"--this also detects the Flip4Mac plugin
}

Phew! That’s that. I can’t guarantee these are the best methods of detecting plugins in Internet Explorer, but they’re the best I came up with. If you’re aware of easier solutions, let me know and I’ll update this post.

Incidentally, if you need a comprehensive solution, I recommend putting these into a class structure—for my project, I used an abstract base class that each plugin class extended, and an overall plugin detection class that called each class’s self-detection method. Extensible, self-contained, and unobtrusive.

Like this post? You might also like Coalmine, my centralized error tracking service for your apps. Coalmine captures errors and all kinds of helpful debugging information, notifies you, and makes it all searchable. Check it out!

22
Jan 07

Prototype has a new website

A few days ago the Prototype core team launched a brand-spanking new website (with documentationfinally!). It’s built on Mephisto, a Rails wiki, and has a clean design like you would expect. Also nice: it has what looks to be a link to the latest compressed snapshot. No more raking!

Like this post? You might also like Coalmine, my centralized error tracking service for your apps. Coalmine captures errors and all kinds of helpful debugging information, notifies you, and makes it all searchable. Check it out!

20
Dec 06

Does Ajax have a place in the application framework?

“Where’s Ajax?” It’s one of the questions that keeps popping up in reference to Zend Framework. I suppose it’s human nature: most people just don’t get as excited about the fundamentals of the project as I do—the MVC implementation, the Lucene-derived search engine [update: not so much], the caching component. They want flashy stuff. They want Ajax. And they don’t want to work too hard for it.

In the last 10 months, application frameworks have scrambled to be Web 2.0-compatible by creating their own Ajax components. In doing so, they have failed to consider the issue of whether they should, and instead concerned themselves primarily with whether they could. Not surprisingly, this has led to questionable implementations.

To determine whether or not a change should be made in my own applications, I apply a rubric. First, is the change essential? If so, the choice is clear; do it. If not, I continue to ask questions. In which ways would the change be beneficial? How would it be harmful? And so on.

Naturally, this same set of questions is applicable to the topic at hand, so let’s examine each in turn.

First, is it essential? Application frameworks are designed for the server-side. What interaction does Ajax have with the server, besides requesting a given URL via GET, POST, or some other method, and interpreting the result? None; this process is the same as a standard Web request. Ajax, therefore, does nothing new or spectacular from the point of view of the server, so it’s not fulfilling a fundamental need.

Is it beneficial? Most Ajax components wrap JavaScript libraries like Script.aculo.us or Dojo, even to the point of using identical method names. For those with a basic understanding of JavaScript, how much time is saved here over using those libraries directly, really? For those without that understanding, is it wise to incorporate technology into your application without knowing how it works, at least broadly? Better to spend a day learning JavaScript and the principles behind XMLHttpRequest and then choose a JavaScript library that best matches your needs and programming style. Why write more lines in Java with Wicket when you could just write JavaScript directly?

As far as I can tell, most of these components exist simply so that developers can stay cozy and warm within the cocoon of their favorite language, without having to venture out into another one. This is a particularly common attitude among Java developers, as Google Web Toolkit illustrates. Although it has plenty of stated benefits (some of them of questionable validity), let’s be frank—the real reason to use it is to never have to write a line of JavaScript. Is JavaScript so bad that it’s better to have to compile it from some other language, especially when excellent debuggers like Firebug exist?

Is it harmful? One of the most basic tenets of modern web development is the separation of presentation and logic, but Ajax components often ignore this concept. Although some implementations involve the creation of entire forms from within the controller, let’s assume a more intelligent design, as a view helper.

Even with a view helper implementation, you’re suddenly presented with a problem: unless the code is prepared in some way prior to run-time (similar to a “compile” step), you’re now most likely mixing JavaScript event handling with your HTML. Some people don’t even realize that this is a problem. However, just as server-side logic should be separated from the view (as with a multitier architecture) and style should be separated from content (as with CSS), behavior has no place within the presentation. That is to say, JavaScript belongs in a separate, included file. It’s called “unobtrusive JavaScript“.

As typically implemented, Ajax components also have the practical effect of preventing those without JavaScript from using the application at all. That includes visitors using screen reading software, most visitors using some sort of mobile browser, and many corporate users straining under IT paranoia. Altogether, these can account for up to 5% of all visitors to a public website. If frameworks are generating inaccessible code, then it is just as much their fault as the fault of the developer using the tool.

Conclusion

It seems clear to me that Ajax components add no real value if the code is not prepared prior to run-time, and little value if it is. Instead of creating Ajax components, framework developers should let JavaScript library developers handle Ajax simplification and focus on other matters. JavaScript is good and it has its place, but not within the application framework.

Like this post? You might also like Coalmine, my centralized error tracking service for your apps. Coalmine captures errors and all kinds of helpful debugging information, notifies you, and makes it all searchable. Check it out!

30
Nov 06

Prototype extensions

Prototype may be ubiquitous, but there’s some functionality it has yet to cover. Here are a couple of useful extensions I’ve run across lately to fill in the gaps:

Cookie, by Carlos Reche

JavaScript already does a pretty good job of getting and setting cookie values on its own, but this extension makes it trivial. The simple Cookie object gives you access to get(), set(), erase(), and accept() methods.

var Cookie = {
  set: function(name, value, daysToExpire) {
    var expire = '';
    if (daysToExpire != undefined) {
      var d = new Date();
      d.setTime(d.getTime() + (86400000 * parseFloat(daysToExpire)));
      expire = '; expires=' + d.toGMTString();
    }
    return (document.cookie = escape(name) + '=' + escape(value || '') + expire);
  },
  get: function(name) {
    var cookie = document.cookie.match(new RegExp('(^|;)s*' + escape(name) + '=([^;s]*)'));
    return (cookie ? unescape(cookie[2]) : null);
  },
  erase: function(name) {
    var cookie = Cookie.get(name) || true;
    Cookie.set(name, '', -1);
    return cookie;
  },
  accept: function() {
    if (typeof navigator.cookieEnabled == 'boolean') {
      return navigator.cookieEnabled;
    }
    Cookie.set('_test', '1');
    return (Cookie.erase('_test') === '1');
  }
};

Event.wheel(e), by Frank Monnerjahn

This tiny extension (about a dozen lines) adds support for mouse wheel events—handy for any number of things, like manipulating a gauge or slider, or scrolling something sideways. Frank based this code on an example by Adomas Paltanavičius.

Object.extend(Event, {
  wheel:function (event) {
    var delta = 0;
    if (!event) {
      event = window.event;
    }
    if (event.wheelDelta) {
      delta = event.wheelDelta/120;
      if (window.opera) {
        delta = -delta;
      }
    } else if (event.detail) {
      delta = -event.detail/3;
    }
    return Math.round(delta); //Safari Round
  }
});

And a usage example:

Event.observe(document, 'mousewheel', yourCallbackFunction, false);
Event.observe(document, 'DOMMouseScroll', yourCallbackFunction, false); // Firefox
Like this post? You might also like Coalmine, my centralized error tracking service for your apps. Coalmine captures errors and all kinds of helpful debugging information, notifies you, and makes it all searchable. Check it out!