Calculated Fields

The CalculatedFields desktop sample demonstrates how to add calculated and lookup fields to a DADataTable. A "calculated" field derives its value based on a calculation using values from other fields, where as a "lookup" field uses a key to lookup and retrieve the required value from another table.

Getting Started

The Desktop sample is typically located in /Developer/RemObjects Software/Samples/Data Abstract/Desktop/CalculatedFields, 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 simply displays the results of adding 3 calculated fields and one lookup field.

When this sample is run you will see an empty table and a toolbar which contains a button and a pop-up. The button "Load Orders" will retrieve the required tables and then add the calculated and lookup fields before displaying the results in the table. 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. In this section we shall focus solely on how you add calculated and lookup fields.

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. Here beginGetData retrieves all of the tables required (Providers, Clients, OrderType, Orders and OrderDetails) using the methods getReferences and getOrders, then setupData makes use of those tables to add the calculated and lookup fields; the code below comes from this class.

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. The main thing of note is that the dataIsReady method is called when the NOTIFICATION_DATA_READY notification is received, which causes the table to update the table with the newly received data.

Adding a Calculated Field

To add a calculated field to a DADataTable you need to either use the addCalculatedFieldName:dataType:target:selector: method of DADataTable, or manually create an instance of DACalculatedFieldDefinition and add it using the addCalculatedField: method.

Here we use the addCalculatedFieldName:dataType:target:selector: method which takes 4 arguments:

  • Name - which is the unique name to be given to the field/column
  • dataType is the data type for the field which should be one of the types defined in DADataType
  • target is the object that will provide the method used for calculating the value of this field
  • selector is the method/selector on the target that will be called to calculate the fields value.

In the sample below, a new calculated field is added to the OrderDetails table, with the name "Sum", which will use the "datCurrency" data type which is used for monetary values, the target is "self" (the DataAccess class) and the calculateSumForDetailRow: is the selector that will be called. The calculateSumForDetailRow: method takes the value from the "Amount" field and multiplies it by the value in the "Price" field and returns that result.

//DataAccess.m
- (void)setupData {

    [self.orderDetailsTable addCalculatedFieldName:@"Sum"
                                          dataType:datCurrency
                                            target:self
                                          selector:@selector(calculateSumForDetailRow:)];

    //...
}

-(id)calculateSumForDetailRow:(DADataTableRow *)row {

    NSInteger amount = [row[@"Amount"] integerValue];
    double price = [row[@"Price"] doubleValue];
    double sum = amount * price;

    return [NSNumber numberWithDouble:sum];
}

Adding a Lookup Field

An alternative to the calculated field, is the lookup field which takes its value from a different table than the source table. There are two ways to add a lookup field, the first is by using the addLookupFieldName:sourceField:lookupTable:lookupKeyField:lookupResultField: method of DADataTable, the other way is to manually define a DALookupFieldDefinition object and pass it to the table using the addLookupField: method.

Here the addLookupFieldName:sourceField:lookupTable:lookupKeyField:lookupResultField: method is used, which takes the following arguments:

  • Name a unique name to be given to the lookup field.
  • sourceField the field that will be used as the key value for matching against the lookupKeyField
  • lookupTable takes a reference to the lookup table
  • lookupKeyField the key field in the lookupTable that will be used to match against the sourceField
  • lookupResultField the field in the lookup table that will be used as the result when the sourceField and lookupKeyField match

Here we are looking up the textual name for an order type, a new field is added with the name TypeName. The Orders table has a Type field which contains a numeric value which acts as a key against the Id field of the OrderType table, when a match is found the value in the Name field of the appropriate row is returned.

//DataAccess.m
- (void)setupData {

    //...
    [self.ordersTable addLookupFieldName:@"TypeName"
                             sourceField:[self.ordersTable fieldByName:@"Type"]
                             lookupTable:self.orderTypeTable
                          lookupKeyField:[self.orderTypeTable fieldByName:@"Id"]
                       lookupResultField:[self.orderTypeTable fieldByName:@"Name"]];
}