02.21.06

Class-based OOP in Javascript done right

Posted on February 21st, 2006 in General by ptw

Max, Adam and I have been working on a scheme to support OpenLaszlo’s LZX language in pure Javascript. As explained in our blog entries, LZX is a blend of class-based and prototype-based object-oriented programming, with an emphasis on class-based programming (because that seems the more widely accepted paradigm). Our current project is to build a new back-end for the LZX compiler to emit “browser Javascript” (i.e., Javascript that will run in all browsers) and to adjust the OpenLaszlo Runtime to also run in browser Javascript. The goal is to make OpenLaszlo run directly in the browser as any good AJAX platform should.

My post over the weekend was our latest version, but right after I posted it, I realized there was still a lot of room for improvement. The basic idea was there, but as Max observed, it’s really not nice to modify the built-in Javascript classes. One might even claim that Object.prototype is verboten. So I spent a little time re-working what I posted, and now I have a really clean version (which I’ll show in a minute).

Adding class-based inheritance to Javascript is a popular pastime. Probably because so many more people are familiar with class-based than prototype-based inheritance. (As I pointed out with my reference to the Treaty of Orlando, they each have strengths and weaknesses — neither is the clear winner).

We’re not trying to replace Javascript’s inheritance mechanism. We’re actually just trying to build on it and make it a little more intuitive for everyone who is used to class-based inheritance. We’re releasing our work as open source to the community (under the CPL license), in hopes of building a consensus that all AJAX platforms could build upon.

Okay, lets cut to the chase. Here’s the latest version of the code with some explanatory material interleaved.

/// 
// Classes for Javascript
// Copyright 2006 Laszlo Systems, Inc.  All Rights Reserved.            
// Use is subject to license terms.

As I mentioned, the license terms are CPL.

// Bootstrap Class class
Class = function () { this.constructor = arguments.callee; };

We make a new class, Class, that will be the factory for creating new classes.

// Bootstrap Instance class
Instance = function () { this.constructor = arguments.callee };
// Instance is a Class
Instance.constructor = Class;

We make a new class, Instance that will be the superclass of all instances of all classes.

// Add a method to an instance
// Creates a closure attached to the method that can be used to
// invoke the next most applicable method by:
// arguments.callee.nextMethod('aMethod').apply(this, ...);
Instance.prototype.addMethod = function(methodName, methodFunction) {
  this[methodName] = methodFunction;
  var owner = this;
  methodFunction.nextMethod = function (methodName) {
    // defer evaluation to permit class change
    return owner.constructor.prototype[methodName];
  }
}

This method is the core of the scheme. When you add a method to an instance of a class using addMethod it annotates the method with a nextMethod function that can be used to invoke the next most applicable method. Normally in class-based OOP, you can’t add methods to instances, but this is one of the features of prototype-based OOP we really want to retain.

// Default init method
Instance.prototype.init = function () {};

Any class can override the init method to customize initialization of the class. This method is invoked by the class instance factory once the instance is fully initialized.

// Add a method to a class by adding it to the class's prototype
Class.addMethod = function(methodName, methodFunction) {
  this.prototype.addMethod(methodName, methodFunction);
}

Here’s the way you add a method to a class that will be available to all instances of that class (even if you add a method to the class after the instance has been created — another feature we retain from prototype-based OOP). How do you do it? You just add the method to the class prototype (see above). You might need to draw a picture if you really want to understand how this works.

// Default Instance factory.  This is how you make a new instance
// of a class
Instance.make = function () {
  var i = new this();
  i.init.apply(i, arguments);
  return i;
}

Here’s how you make a new instance of a class. A class can override this method to create a singleton class, or to create a class of immutable instances that can be reused.

Finally, we tie it all together with the Class class factory — the factory that makes new instances of the class Class. As explained in my blog entry, we believe that a factory is a better approach than new, because it gives us better control over creation and initialization. [We notice that many Javascripts support returning a value from new, if this becomes an accepted standard, it would work just as well.]

// Class class factory. This is how you make a new class
Class.make = function (superclass) {
  // The constructor notes itself in every instance
  var nc = function () { this.constructor = arguments.callee; };

As mentioned in my blog entry, our constructors set the constructor property on each instance to reflect the actual constructor, because when you have inheritance by delegation, the constructor property of your prototype will be your superclass, not your class.

  nc.constructor = Class;

So we do that. We are making a new Class, so we make its constructor be Class (not Function as it would be by default).

Next we add two class methods to the new class:

  nc.addMethod = this.addMethod;

You add a method to any class using the Class.addMethod method. This needs to be a class method so you don’t get the instance method which is on the Class prototype.

Finally, we set the prototype for the class to be the superclass that was passed in, or, if there isn’t one, to be Instance. All classes are thus instanceof Instance. (They are also instanceof Object, since Instance is an object.) And add the second class method.

  if (arguments.length < 1) { superclass = Instance; }
  nc.make = superclass.make;
  nc.prototype = new superclass();
  return nc;
}

Note the class make method is defaulted to that of its superclass. By default this will be the instance factory, although it can be overridden by the class.

That’s it! No built-in prototypes need to be modified. Everything is encapsulated in the classes Instance and Class. For the record, I’ve included Adam’s most complex test case below, that shows how you write class-based style code using our system, and that the system works.

<script type="text/javascript" language="javascript1.5">
// <![CDATA[

Make a top class with one method

    mysuper = Class.make();
    mysuper.addMethod("amethod", function (){ return 1; });

Make a subclass of that, overriding the superclass method, but also calling it using nextMethod

    mysub = Class.make(mysuper);
    mysub.addMethod("amethod", function(){ 
        return 10 + arguments.callee.nextMethod( "amethod" ).call(this); 
    });

Make a sub class of that, with another override

    mysubsub = Class.make(mysub);
    mysubsub.addMethod("amethod", function(){ 
        return 0.1 + arguments.callee.nextMethod( "amethod" ).call(this); 
    });

Add some more methods. Note that this method, on the top-most class, calls another method, which should be the _most_ _specific_ method according to the runtime class of this, which in this case will be mysubsub.dmethod.

    mysuper.addMethod("cmethod", function ( ){
        return this.dmethod();
    });
    mysuper.addMethod("dmethod", function ( ){ return 0.1; });
    mysub.addMethod("cmethod", function ( ){
        return 1 +
        arguments.callee.nextMethod( "cmethod" ).call(this);
    });
    mysub.addMethod("dmethod", function ( ){
        return 10 +
        arguments.callee.nextMethod( "dmethod" ).call(this);
    });
    mysubsub.addMethod("cmethod", function ( ){
        return 100 +
        arguments.callee.nextMethod( "cmethod" ).call(this);
    });

This will be the method called by mysuper.cmethod. If inheritance is working properly, nextMethod should call the corresponding mysub method. This is where some other class-based schemes break down, as Adam showed.

    mysubsub.addMethod("dmethod", function ( ){
        return 1000 +
        arguments.callee.nextMethod( "dmethod" ).call(this);
    });
    asubsub = new mysubsub();
// ]]>
</script>
<button onclick='
    alert( "asubsub.cmethod( ) == 1111.1? " +
        ( asubsub.cmethod( ) == 1111.1 ) + "\n" + 
        "It is really: " + asubsub.cmethod( ) );
'>test five
</button>

Try it:

02.03.06

OpenLaszlo running on a cell phone!

Posted on February 3rd, 2006 in Development by jgrandy

I wanted to share a bit of excitement we had this week. A little while ago, Macromedia announced availability of Flash Lite 2, a new version of its Flash player for mobile devices. This new version is Flash 7 compatible, which is great because of course OpenLaszlo runs on Flash 7.

So we ordered one of the supported phones (an unlocked Nokia 6680 — very nice), and it arrived on Tuesday. In twenty minutes, I had the FL2 player transferred via bluetooth and was playing a sample Flash game on the phone. Twenty more minutes and I had this running:

Clock on Phone

If you aren’t familiar with the image on the screen, compare with this.

All I did was wrap the clock code in a new LZX file with the right canvas dimensions (208 x 176), compile it SOLO using lzc, and transfer the swf file to the phone via bluetooth. The file showed up in my messaging inbox, and a click opened it into the Flash Lite 2 player. Launch time was about three seconds, and performance was quite acceptable.

Next steps are to try out the keypad, and to test network access. How exciting to see an OpenLaszlo app running on a cell phone!