"I have a mind like a steel... uh... thingy." Patrick Logan's weblog.

Search This Blog

Sunday, August 21, 2011

Programming with Cappuccino's CPOutlineView

I wrote in my recent post on Cappuccino about the OK but not great state of documentation. My learning process has been smooth though. I have always had a sense of moving forward with little frustration. But I should attempt to fill some of the documentation gaps myself.

I recently wanted to understand how to program outline views, but ran into some of those gaps. And so my second blog post of the day begins like so.

CPOutlineView is a subclass of CPTableView however from the code using an outline, the requirements are different. Rather than providing a two-dimensional array of data, an application has to provide more of a hierarchy of two-dimensional data. The way I achieve this in my first example is through defining a new class, an SDOutlineController.

(Don't worry about "SD" - that's just the class prefix I am using. Class prefixes are a convention going way back with NeXTStep and before that Smalltalk. Presumably Javascript itself will adopt a conventional if not standard namespace mechanism, and Objective-J can piggyback on that. But I digress...)

This example outline has two groups at the top level: "my stuff" and "other's stuff". Each top-level group has three second-level groups of priorities: "high", "medium", and "low". The third level of the outline are the lists of stuff, themselves. These are arrays of instances of SDStuff.

Here's the class definition for stuff with instance variables for a date, a topic, and notes:

@implementation SDStuff : CPObject
{
  CPDate   date  @accessors(readonly);
  CPString topic @accessors(readonly);
  CPString notes @accessors(readonly);
}

This is Objective-J, a superset of Javascript. I'm assuming that's fairly readable even if you've not programmed in Objective-C and Cocoa. Objective-J can be compiled to Javascript up-front on the server, or in the browser.

The SDOutlineController is the C in MVC for this outline. The V is Cappuccino's CPOutlineView class (and some others). The controller's job is to work with an application's model (the M) in order to present it in the view for user interaction. The model in this case is a little controved, but boils down the essence of a hierarchical model.

I have defined the top-level of the model hierarchy to be a simple class with a label and references to it's children, which group stuff into high, medium, and low priorities:

@implementation SDStuffTopLevel : CPObject
{
  CPString       label                     @accessors(readonly);
  SDStuffParent  highPriorityStuffParent   @accessors(readonly);
  SDStuffParent  mediumPriorityStuffParent @accessors(readonly);
  SDStuffParent  lowPriorityStuffParent    @accessors(readonly);
}

The middle of the hierarchy is similarly defined, but the children are maintained as an array of stuff:

@implementation SDStuffParent : CPObject
{
  CPString label @accessors(readonly);
  CPArray  stuff @accessors(readonly);
}

The model classes above organize data into a fairly simple hierarchical structure. However this structure does not suit all imaginable hierarchies. And so SDOutlineController is designed to avoid having the view depend on any given structure, and vice-versa. The controller has a reference to the top-level for my stuff and the top-level for other's stuff:

@implementation SDOutlineController : CPObject
{
  SDStuffTopLevel myStuffTopLevel;
  SDStuffTopLevel othersStuffTopLevel;
}

The controller has methods expected by CPOutlineView for mediating between the model and the view. The messages are:

  • outlineView:numberOfChildrenOfItem: is kind of obvious.
  • outlineView:isItemExpandable: should be YES (true) if the item has children.
  • outlineView:child:ofItem: should access a hierarchical node's children given an index.
  • outlineView:objectValueForTableColumn:byItem: should provide an object for representing the given model element in the view.

In this example, the top-level of the model always has two elements. The protocol for CPOutlineView uses the convention of passing nil (i.e. the Objective-J equivalent of Javascript's null.) to indicate the root of the model. And so I have not implemented the root as a class.

The following method provides the number of children for any given model element because I have implemented methods for the count message for each model class.

- (int)outlineView:(CPOutlineView)outlineView numberOfChildrenOfItem:(id)item
{
  return (item == nil) ? 2 : [item count];
}

Expandability of a hierarchical element is based on the number of children being greater than zero:

- (BOOL)outlineView:(CPOutlineView)outlineView isItemExpandable:(id)item
{
  return 0 < [self outlineView: outlineView numberOfChildrenOfItem: item];
}

Indexing the child of a hierarchical element is a little more complicated, again just because the root is represented as nil. Otherwise I have implemented objectAtIndex: for the model classes:

- (id)outlineView:(CPOutlineView)outlineView child:(int)index ofItem:(id)item
{
  var result = nil;
  if (nil == item) {
    switch (index) {
    case 0:
      result = myStuffTopLevel;
      break;
    case 1:
      result = othersStuffTopLevel;
      break;
    }
  } else {
    result = [item objectAtIndex: index];
  }
  return result;
}

The remaining method determines the the presentation object for a given model element. The root is never displayed, but to be compelete, this method returns the empty string. Every model class implements objectValueForOutlineColumn: in order to convert itself to its presentation object (which is always a string in this example).

- (id)outlineView:(CPOutlineView)outlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item
{
  return (nil == item) ? @"" : [item objectValueForOutlineColumn: tableColumn];
}

I will use a follow-up blog post to discuss the implementation of objectValueForOutlineColumn:. Perhaps this post provides a little more clarity than other information I've found on the web for programming with CPOutlineView. Let me know if you have a correction or something to add.

No comments:

Blog Archive

About Me

Portland, Oregon, United States
I'm usually writing from my favorite location on the planet, the pacific northwest of the u.s. I write for myself only and unless otherwise specified my posts here should not be taken as representing an official position of my employer. Contact me at my gee mail account, username patrickdlogan.