Extending Classes in Cappuccino

September 7, 2008. Filed under cappuccino 5

One of the nicest things I have found about Cappuccino is its ease of customization. It's often assumed that heavy frameworks limit your options, but with good design they can also expose customization logic in such a way that it is quick and easy to create widgets that perform exactly how you want1.

When I was putting together the first draft of Mahou, I was having trouble finding an intuitive way to provide a hyperlink within my application. Cappuccino focuses on application development, and doesn't play particularly well with the internet at large (although, this is the matter of widgets being created, not an insurmountable barrier).

My solution to the click issue was to subclass CPTextField to create a textfield that takes you to an arbitrary URL when clicked. I'll use this simple example as a brief introduction to extending Cappuccino.

  1. The first step to extending Cappuccino is to figure out which class to subclass. The Cappuccino documentation is quite good, and helps introduce all the various classes to chose from.

  2. Once you've picked the class to subclass, you have to figure out where in the inheritance tree to subclass it. For example, the CPTextField has a fairly long inheritance chain: CPObject -> CPResponder -> CPView -> CPControl -> CPTextField.

    This is usually accomplished by stepping up the inheritance tree and looking for appropriate methods. The different classes focus on different subsets of functionality (CPView handles sizing and placement, CPResponder handles UI events, CPControl handles UI display, and so on).

    In my case, I knew that the functionality I wanted to override would lie somewhere in the CPResponder class, because I wanted to customize responding to a UI event.

  3. We continue to focus in by figuring out the specific class methods we'll want to override. For my purposes, the textfield needed to do something when clicked, so I browsed through the documentation and decided I would need to subclass either the mouseDown: or mouseUp methods.

    After flipping a coin (and weighing UI responsiveness against the principle of least surprise) I decided to go with mouseDown:.

  4. Finally we need to implement the functionality. We want to open up a new window which retrieves a specific webpage. I couldn't find a way to do that in Objective-J, but fortunately I know exactly how to do that in JavaScript, and Objective-J is a superset of JavaScript, so anything I can do in JavaScript I can do in Objective-J.

    The first draft of the Objective-J code came out like this:

    import <Foundation/CPObject.j>
    import <AppKit/CPTextField.j>
    
    @implementation WLURLLabel : CPTextField {}
    
    -(void)mouseDown:(CPEvent)anEvent {
        window.open("http://lethain.com");
    }
    @end
    
  5. However, it seemed pretty likely that I would end up wanting more than one clickable link, so hardcoding the URL wasn't really going to cut it. So I made the five minute investment to make the code reusable.

    import <Foundation/CPObject.j>
    import <AppKit/CPTextField.j>
    
    @implementation WLURLLabel : CPTextField {
        CPString _url;
    }
    
    -(void)mouseDown:(CPEvent)anEvent {
        window.open([self url]);
    }
    
    -(CPString)url {
        return _url;
    }
    
    -(void)setUrl: (CPString)aString {
        _url = aString;
    }
    @end
    

    Ah. The joys of accessors and mutators permeate Cappuccino just as they do Cocoa.

  6. The last step is to use WLURLLabel in some code, which will look like this:

    var myFrame = CGRectMake(0,0,100,30);
    var label = [[WLURLLabel alloc] initWithFrame:myFrame];
    [label setStringValue:@"Click here to visit Google."];
    [label setUrl:@"http://www.google.com"];
    [label setFont[CPFont boldSystemFontOfSize:12.0]];
    // changing color makes links more obvious
    [field setTextColor:[CPColor whiteColor]];
    [contentView addSubview:label];
    

All in all, customizing content in Cappuccino is a fairly simple process. What isn't obvious from such a simple example is that even making large and seemingly complex changes will follow this exact same pattern.

Many of the advantages here are simply because of the import mechanism in Objective-J. Having a coherent import mechanism makes it easy to spread your code out into multiple organized files, which makes code more reusable and modifications quicker to make.

Let me know if you there are any questions or comments!


  1. For those who may be interested, I am simply using the Pygment's Objective-C syntax highligher for the snippets of code. It ends up doing a pretty reasonable job.