TableView Delegate

The last time we created a simple table view (source). Today we will add a delegate in order to be informed about selection changes in our table. Delegates are a common way to inform another object about changes or perform specific tasks and react on the returned value.

To add a delegate to our table view all we have to do is add a single line to our existing code:

var tableView = [[CPTableView alloc] initWithFrame:[scrollView bounds]];
[tableView setDataSource:self];
[tableView setDelegate:self]; // <<--- add this line

var column = [[CPTableColumn alloc] initWithIdentifier:@"Stuff"];

There are plenty methods our delegate can (but must not) react to

  - (BOOL)selectionShouldChangeInTableView:(CPTableView)aTableView
  - (CPView)tableView:(CPTableView)tableView dataViewForTableColumn:(CPTableColumn)tableColumn row:(int)row
  - (void)tableView:(CPTableView)tableView didClickTableColumn:(CPTableColumn)tableColumn
  - (void)tableView:(CPTableView)tableView didDragTableColumn:(CPTableColumn)tableColumn
  - (float)tableView:(CPTableView)tableView heightOfRow:(int)row
  - (BOOL)tableView:(CPTableView)tableView isGroupRow:(int)row
  - (void)tableView:(CPTableView)tableView mouseDownInHeaderOfTableColumn:(CPTableColumn)tableColumn
  - (int)tableView:(CPTableView)tableView nextTypeSelectMatchFromRow:(int)startRow toRow:(int)endRow forString:(CPString)searchString
  - (CPIndexSet)tableView:(CPTableView)tableView selectionIndexesForProposedSelection:(CPIndexSet)proposedSelectionIndexes
  - (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex
  - (BOOL)tableView:(CPTableView)aTableView shouldSelectRow:(int)rowIndex
  - (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn
  - (BOOL)tableView:(CPTableView)tableView shouldShowCellExpansionForTableColumn:(CPTableColumn)tableColumn row:(int)row
  - (BOOL)tableView:(CPTableView)tableView shouldTrackView:(CPView)view forTableColumn:(CPTableColumn)tableColumn row:(int)row
  - (BOOL)tableView:(CPTableView)tableView shouldTypeSelectForEvent:(CPEvent)event withCurrentSearchString:(CPString)searchString
  - (CPString)tableView:(CPTableView)aTableView toolTipForView:(CPView)aView rect:(CPRectPointer)rect tableColumn:(CPTableColumn)aTableColumn row:(int)row mouseLocation:(CPPoint)mouseLocation
  - (CPString)tableView:(CPTableView)tableView typeSelectStringForTableColumn:(CPTableColumn)tableColumn row:(int)row
  - (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex
  - (void)tableViewSelectionDidChange:(CPNotification)aNotification
  - (void)tableViewSelectionIsChanging:(CPNotification)aNotification

Most of them are pretty obvious when they are called. For detailed information you can visit the apple documentation for the NSTableViewDelegate Protocol.

As an example we now can simply implement any of these delegate methods in our AppController

- (void)tableViewSelectionDidChange:(CPNotification)aNotification
{
  row = [[[aNotification object] selectedRowIndexes] firstIndex];
  console.info(row);
}

after reloading our index.html you should see the index of the current selected row (or -1 if nothing is selected) in your browsers console

Now you can do anything you like in order to react on selection changes. (Code on github).

Simple TableView

First of all create a new project using (Assuming you already have installed the latest Cappuccino release (0.8.1 as I’m typing):

capp gen simple-tableview

This should create a file called AppController.j inside the simple-tableview directory. Get rid of the standard code so your AppController.j file looks like this:

/*
 * AppController.j
 * simple-tableview
 *
 * Created by Stefan Huber on June 5, 2010.
 * Copyright 2010, MSNexploder (at) gmail.com All rights reserved.
 */

@import 

@implementation AppController : CPObject
{
}

- (void)applicationDidFinishLaunching:(CPNotification)aNotification
{
    var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask];
    var contentView = [theWindow contentView];
    
    // table view related code goes here..
    
    [theWindow orderFront:self];
}

@end

A table view does not itself have any mechanism to scroll if there are too many rows to display, so we embed it in a scrollview. Let’s create one now. Replace our comment with the following:

var scrollView = [[CPScrollView alloc] initWithFrame:[contentView bounds]];
[scrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];

This creates a fullscreen scroll view. See Cappuccino automatic-layouts for a very good explanation of what setAutoresizingMask is and how you use it.

Next, we add the table view itself:

var tableView = [[CPTableView alloc] initWithFrame:[scrollView bounds]];

Additionally we set the data source of the table view. Normally you would use a separate class but in this example we will just use our AppController:

[tableView setDataSource:self];

To use a object as data source for a table view you must implement these two methods:

// this tell the table how many rows it has…
- (int)numberOfRowsInTableView:(CPTableView)aTableView;

// this defines what text will display for each row, in each column, for each table view.
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)aRow;

We will add them later…

Now create a new table column, set its header and add it to the table view:

var column = [[CPTableColumn alloc] initWithIdentifier:@"Stuff"];
[[column headerView] setStringValue:@"Stuff"];
[tableView addTableColumn:column];

To finish our applicationDidFinishLaunching: method simply assemble all our views:

[scrollView setDocumentView:tableView];
[contentView addSubview:scrollView];

All what’s missing now are the two data source delegate methods, so just add a very basic version of them:

- (int)numberOfRowsInTableView:(CPTableView)aTableView
{
  return [_tableContent count];
}

- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)aRow
{
  return [_tableContent objectAtIndex:aRow];
}

As you can see we use an object called _tableContent.  It’s a simple CPArray containing all the informations needed for our table view. So let’s go ahead and create it.

Because we need to reference this array elsewhere in the class, we’ll make this a class level variable, so at the top of the file where we have the implementation for AppController, define the new array:

@implementation AppController : CPObject
{
  CPArray _tableContent;
}

Finally we must initialize and populate the CPArray with some data. We will do this in the AppController init method.

- (id)init
{
  if (self = [super init]) {
    _tableContent = [[CPArray alloc] init];
    [_tableContent addObject:@"Cappuccino"];
    [_tableContent addObject:@"Awesome"];
    [_tableContent addObject:@"Stuff"];
  }
  return self;
}

Thats it, you should end up with something like this:

The whole code is available on github.