A plea to IE

Please, please implement getters and setters in your JavaScript engine. I cannot tell you how much I want to add this to JS.Class:

var MagicMethods = {
  included: function(klass) {

    var define = function(object, name) {
      var shortName = name.replace(/^[gs]et[A-Z]/,
          function(s) { return s.charAt(3).toLowerCase() });

      if (/^get[A-Z]/.test(name))
        object.__defineGetter__(shortName,
          function() { return this[name]() });

      if (/^set[A-Z]/.test(name))
        object.__defineSetter__(shortName,
          function(value) { return this[name](value) });
    };

    for (var key in klass.prototype)
      define(klass.prototype, key);

    if (klass.instanceMethod)
      klass.instanceMethod = (function(wrapped) {
        return function() {
          var name = arguments[0];
          define(klass.prototype, name);
          return wrapped.apply(klass, arguments);
        };
      })(klass.instanceMethod);
  }
};

This mixin needs a few type checks adding but I didn’t want to obscure the main idea. When mixed into a class, MagicMethods will intercept all instance method definitions and, if the method name looks like getX or setX it will add a shorthand getter or setter to the class. So, for example, the main collection class in Ojay has a bunch of methods like setStyle, getRegion and what-not. If we use the above mixin, we can get more intuitive code:

Ojay.DomCollection.include(MagicMethods);

// Set style of all paras
$('p').setStyle({fontStyle: 'italic'});    // old
$('p').style = {fontStyle: 'italic'};      // new

// Find region of the title
var region = $('h1').getRegion();    // old
var region = $('h1').region;         // new

Granted, I’m not saving a huge amount of typing here, but getters and setters get you out of the trap of having to make everything a method rather than a property to enforce encapsulation. style= and region in the above code look like property accesses, but are really function calls to setStyle() and getRegion() under the covers. You can implement something as a property to begin with, then later on you can change it to a getter method without breaking anyone’s code. It really comes into its own when using MethodChain:

MagicMethods.included(JS.MethodChain);

$('p').on('mouseover').style = {color: 'red'};
$('a').on('click', $.stopDefault).content = 'Clicked!'

This makes for a more declarative style for doing simpe event handling.

Anyways, this is all a moot point because this technique only works in Firefox and Safari. If we’re going to count beta versions, Opera 9.5 supports it too (thanks to John Resig for the heads-up). I’ve no idea whether IE8 plans to support it, and I don’t think it’s in the ECMAScript standard (3 or 4) so they’ve really no obligation to (comments proving me wrong greatly appreciated). In any case, the need to support IE 6 and 7 for the foreseeable future means I can’t use this feature, and I’m pretty sure there’s no way to reliably fake it in IE. Any ideas on 3-by-5 cards, please.

JS.Class 1.6.0: Forwardable, State, and Ruby

A little update: JS.Class 1.6.0 is now out. The main new features are a port of Ruby’s Forwardable module for method delegation, an implementation of the State pattern (which I’m using heavily for building UI code), and JS.Ruby, which is something I wrote about a few weeks back. Also, I’ve implemented the extended hook to complement included; all Module objects now implement both of these, making it easier to user modules do define class methods. Head over to the site and give it a whirl.

Automated example code displays

I’ve been writing some code examples for some of the UI components I’m writing for Ojay, and I need to display the implementation code and stylesheet on the page. Pretty standard fare: here’s a UI example, and here’s the code you need to implement it. Easy.

Trouble is, I don’t want to duplicate the code (once in a script block, once displayed on the page), especially while editing. It’s only going to get out of sync at some point, and it’s tedious to maintain. So, I cooked up a way of getting my page to display its own source code as part of the example. First thing you’re going to need is a script block and a textarea to display its source:

<script type="text/javascript" id="the-script">
    // code goes here...
</script>

<textarea id="the-display"><textarea>

You throw your example code in the script block and it sets up the example UI you’re demonstrating. Then, all you need to do is extract its contents and put the code in the textarea for display. (It doesn’t have to be a textarea, although you can be sure it will preserve line breaks that other elements might throw out.) In another script block, put the following:

var code = $('#the-script').node.innerHTML.unindent();
$('#the-display').setAttributes({
    value: code,
    rows: code.split(/\n/).length + 1
});

Simple enough, now you can be sure the example code that people read will match what’s actually running on the page. Just one thing is missing: that unindent() function. I needed this because the script gets indented quite a lot by my templating system, which produces nice nested easy-to-read HTML. I need to strip off the common indentation from all the lines to just leave the raw code. To do this, we find the minimum indentation of all the lines and then remove it from each line of the string, like so:

String.prototype.unindent = function() {
    var lines = this.replace(/^(\s*\n)*/, '')
            .replace(/(\n\s*)*$/, '').split(/\n/);
    var indent = lines.reduce(function(memo, line) {
        if (/^\s*$/.test(line)) return memo;
        var spaces = line.match(/^ +/);
        if (!spaces) return memo;
        return Math.min(memo, spaces[0].length);
    }, Infinity);
    return lines.map({slice: indent}).join("\n");
};

Not rocket science, just a nice demo of some functional programming and one less bit of duplication in my codebase. It works with inline style elements as well, but so far I’ve not found any reliable way of doing it with HTML code while maintaining line breaks in the display (at least, not without manually parsing the HTML myself). If anyone knows how I might do this then do leave some advice in the comments.