DADataTableController
The DADataTableController desktop sample demonstrates how to use the DADataTableController helper class which is a bindings compatible class that can be used to manage a DADataTable and mediate between a view (NSTableView
) and the model (DADataTable
).
Getting Started
The Desktop sample is typically located in /Developer/RemObjects Software/Samples/Data Abstract/Desktop/DADataTableController
, though you may have installed the Data Abstract for Cocoa and it's samples in another location.
To build it, you will of course need Xcode, and like all the samples provided with Data Abstract for Cocoa you will need to be running the Relativity Server with the DASamples
Domain and the Simple
Schema available.
Running the Sample
This sample shows just how easy it is to integrate DADataTableController into your code.
When you run the sample you will see an empty table, and a toolbar with 4 buttons and a popup. The "Load" button retrieves "Clients" table and displays the results in the table. The "Add" button adds a new row to the DADataTable. The "Remove" button will be enabled when a row is selected and will delete it from the local DADataTable. The "Apply" button sends a delta of the changes to the server and applies the changes there. Finally the "Servers" popup contains a list of available servers discovered by the Zeroconf discovery system. When the sample is run, it starts a Zeroconf discovery service that looks for instances of Relativity Server running on the local network. Any discovered servers will be added to the popup, if no servers are discovered then the dropbox on the toolbar will be empty. You can specify a server address yourself by clicking on the popup and then clicking on "Specify custom URL...".
Examining the Code
The code for creating the connection to the server and handling logging in is covered by other samples, likewise other samples cover manipulating the DADataTable and applying changes back to the server.
At its core the DADataTableController is inherited from NSArrayController
and therefore acts in a similar manner. The main difference is that there is an additional property, table, which represents the DADataTable we want to manage with the DADataTableController class.
NOTE If the table name, or the field names do not exist then an ROException
will be raised and should be handled appropriately.
App Structure
The sample is built around four classes; AppDelegate
, DataAccess
, ServiceAccess
, and RegisterServiceWindowController
.
The DataAccess
class handles everything related to interacting with the Data Abstract SDK; including retrieving data from an instance of Relativity Server and applying changes back to the server.
The ServiceAccess
class handles the discovery of any instances of Relativity Server that are available on the local network using ROZeroConf which is a feature available with the Remoting SDK that Data Abstract is built upon. ServiceAccess
sets up a ROZeroConfBrowser Class object which searches for any servers that broadcast a value matching the value defined for RELATIVITY_SERVICE_NAME
. When a service is found, or indeed disappears, the server list is updated and the popup with the list of available servers is also updated. It also handles the registration of custom server addresses. To explore further the ServiceAccess
class and Zeroconf discovery see the article: The ServiceAccess Class and Zeroconf discovery.
The RegisterServiceWindowController
class is a subclass of NSWindowController
which handles the UI aspects of a user manually adding a server url.
Lastly the AppDelegate
class handles the primary setup of the application, registers that it will listen for notifications broadcast by the ServiceAccess
and DataAccess
classes, and acts as a delegate to the RegisterServiceWindowController
, DataAccess
and NSTableView
classes.
Setting up DADataTableController
Setting up your code to use the DADataTableController can be achieved in just a few steps.
In your UI file, be it xib or Storyboard, add a Array Controller from the objects menu and set its "Custom Class" to DADataTableController. Switch to the "Attributes Inspector" and specify the keys (the field names in the table) that will be used.
The for each column in your table view, switch to the "Bindings Inspector" and enter the name of the appropriate key for the column in the "Model Key Path" field. Here in the figure above you can see that for the "Id" column, the "Model Key Path" field has "ClientId" entered.
To give the DADataTableController content to display, we need to assign the DADataTable that is retrieved when the "Load" button is pressed. Here dataIsReady:
is called when the NOTIFICATION_DATA_READY
is received and it assigns the the retrieved data table to the IBOutlet for the array controller.
//AppDelegate.m
- (void)dataIsReady:(id)notification {
NSLog(@"DATA IS READY...");
dispatch_async(dispatch_get_main_queue(), ^{
self.tableController.table = self.dataTable;
});
}
Adding a new row
To add a new row to the table is as easy as calling the add:
method of the DADataTableController, it will then automatically be selected in the table view.
Here we make use of this and we retrieve the newly selected role and specify default values to some of the fields. Finally the newly added row in put into an edit state in the "Name" column.
//AppDelegate.m
- (IBAction)add:(id)sender {
// add new row using DADataTableController
[self.tableController add:sender];
// obtain just added row and set some defaults there
DADataTableRow *addedRow = [[self.tableController selectedObjects] firstObject];
addedRow[@"ClientName"] = @"[new client]";
addedRow[@"ClientEmail"] = @"temp";
addedRow[@"ClientPassword"] = @"123";
// move added row into edit state in NSTableView
[self.tableView editColumn:1 row:[self.tableController selectionIndex] withEvent:nil select:YES];
}
Removing a row
To remove a row from the DADataTableController you use the remove:
method passing in the table view. This will delete the selected rows.
Here, we retrieve the selected row and present a dialog using information from the row to confirm that the user really wants to delete the row. If they reply in the affirmative, then the table controllers remove:
method in called.
//AppDelegate.m
- (IBAction)remove:(id)sender {
// obtain selected row for removing attempt
DADataTableRow *rowToRemove = [[self.tableController selectedObjects] firstObject];
// get confirmation from the user
NSString *customerText = [NSString stringWithFormat:@"%@ [id=%d]", rowToRemove[@"ClientName"], [rowToRemove[@"ClientId"] intValue]];
NSAlert *alert = [NSAlert alertWithMessageText:@"Confirm Remove Action"
defaultButton:@"Remove"
alternateButton:@"Cancel"
otherButton:nil
informativeTextWithFormat:@"Do you really want to remove client\n %@", customerText];
[alert beginSheetModalForWindow:self.tableView.window completionHandler:^(NSModalResponse returnCode) {
if (returnCode == NSAlertDefaultReturn) {
// remove selected row using DADataTableController
[self.tableController remove:self.tableView];
}
}];
}
Adjusting the toolbar
In this sample we make use of some of NSObjectController
properties that DADataTableController inherits. The property canAdd
returns true
when it is possible to add a row, this is used to control the state of the "Add" button. To control the "Remove" button we use the canRemove
property which will return true
when a row is selected and false otherwise. Finally we use the hasChanges property of DADataTable to control whether the "Apply" button is enabled.
//AppDelegate.m
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
NSInteger index = [self.serversPopup indexOfSelectedItem];
BOOL hasServer = ((index >= 0) && (index < (self.serversPopup.itemArray.count - 2)));
if ([[theItem itemIdentifier] isEqualToString:@"load"]) {
return hasServer;
}
if ([[theItem itemIdentifier] isEqualToString:@"add"]) {
return self.tableController.canAdd;
}
if ([[theItem itemIdentifier] isEqualToString:@"remove"]) {
return self.tableController.canRemove;
}
if ([[theItem itemIdentifier] isEqualToString:@"apply"]) {
return self.tableController.table.hasChanges;
}
return YES;
}