Profile photo of Alex


Using NSPredicateEditor with DADataTable

January 18, 2010 in Bugs 7, Cocoa, Data Abstract, Mac, Xcode

Today I want to talk about NSPredicateEditor and how to use it with a DADataTable.

What is NSPredicateEditor…

Cocoa’s NSPredicateEditor class provides an excellent way to compose predicates at runtime. The main benefit is that the user needs to know nothing about predicates and their format, but with help of NSPredicateEditor can easily build quite complex conditions they need. Using NSPredicateEditor also allows to avoid any kind of typing errors, because in most of cases all the user needs is to do is just to select appropriate fields and values from popup buttons.

Here is the screenshot of Bugs 7 showing NSPredicateEditor that represents following condition: (Live == 1 AND AreaID == 4 AND OwnerID == 5) OR ID == 37222

NSPredicateEditor uses a list of NSPredicateEditorRowTemplate objects, where each row template describes certain elementary comparison predicate object.  Combining different row templates allows the NSPredicateEditor to represent compound predicates with unlimited complexity.

Usually, a row template holds three elements: left and right expressions and a condition between them. In our case, the left expression represents the column from our table and the right expression represents the value pattern for that column. Obviously, it is good to have template with most suitable editor for each column data type. DataAbstract for OS X provides proper templates for each fields that can be used in the table. In addition to standard  NSPredicateEditorRowTemplate DA brings several custom row templates that simplify building predicates for the tables.

The most interesting here is the DALookupFieldPredicateEditorRowTemplate class. This class allows to create row template for a lookup field, where the user can chose certain lookup value from list of allowed values, proposed by a popup button. It allows user to deal with meaningful values instead of just IDs. This is well shown in the figure above.

I should also mention DAFallbackPredicateEditorRowTemplate. This class describes special template for any predicate that is not supported by a given set of templates. For example if your editor tries to render a NSPredicate that comes into your application from outside and cannot understand a certain part of the compound predicate, then it will render it as “unsupported predicate” with help of DAFallbackPredicateEditorRowTemplate. This assures that the sub-predicate, while not editable in the NSPredicateEditor, will not get lost.

…and How Can You Use it?

Let’s see how to configure NSPredicateEditor instance for composing predicates for a certain custom DADataTable.

When you need to configure the editor for exposing a sub-set of columns only, you need to create array of row templates obtained from each table field you want to see in editor:

NSMutableArray *rowTemplates = [NSMutableArray arrayWithCapacity:[[da bugs] fieldCount]];
for (NSString *f in allowedFields)
  DAFieldDefinition *fd = [[da bugs] fieldByName:f];
  [rowTemplates addObjectsFromArray:[fd predicateEditorRowTemplates]];

Then you should supplement that array with additional row template for building compound predicates

NSPredicateEditorRowTemplate *compoundTemplate =
  [[[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes:
    [NSArray arrayWithObjects:
      [NSNumber numberWithInt: NSAndPredicateType],
      [NSNumber numberWithInt: NSOrPredicateType],
      [NSNumber numberWithInt: NSNotPredicateType],
      nil]] autorelease];
[rowTemplates addObject:compoundTemplate];

and (optionally) add an instance of DAFallbackPredicateEditorRowTemplate for rendering unsupported predicates.

[rowTemplates addObject:[DAFallbackPredicateEditorRowTemplate rowTemplate]];

Finally, you should assign that templates array to your NSPredicateEditor instance

[editor setRowTemplates:rowTemplates];

If you want to expose all visible fields in the editor, then you can just use the convenient defaultPredicateEditorRowTemplates] method on the table, and whole configuration will be reduced to single line of code, as followings:

[editor setRowTemplates:[[da bugs] defaultPredicateEditorRowTemplates]];

That’s all. Your predicate editor is properly configured and you can use it for building any kind of predicates for given table.

You can use that editor to let the user define NSPredicates both for local filtering data in table like:

NSPredicate *p = [editor predicate];
rows = [[[myTable rows] filteredArrayUsingPredicate:p] retain];
[tableView reloadData];

or for creating Dynamic Where for server-side filtering data.

NSPredicate *p = [editor predicate];
DADynamicWhereClause *clause = [DADynamicWhereClause dynamicWhereClauseWithPredicate:p];
request = [rda beginGetDataTable:@"MyTable"
                          select:[self fieldsToSelect]
[request setDelegate:self];
[request start];

I recomend to review our DAFilters sample that ships with DA/OSX, to see NSPredicateEditor and the techniques shown here in action.

2 responses to Using NSPredicateEditor with DADataTable

  1. Very interesting article. Can you recommend a comparable tool for use with Delphi under Windows?

    • Mark, the closest thing to this in Delphi would be a third party component such as Korzh’s EasyQuery ( i’ve used that a long long time ago (before we even had DA, at the time i was using a custom infrastructure i had developed on top of DataSnap) and it worked quite well. Some manual work was needed to properly fill the query builder with the necessary meta data (similar to what’d be needed if you filled an NSPredicateEditor manually), but it was doable.

      i suppose it might be worth investigating if we could provide integration with Korzh EasyQuery (both for DA/Delphi and Da/.NET) – i’ll bring this up for internal discussion, here.