Creating a daterange search field

From EOFWiki
Jump to navigationJump to search

Intro

In this little article a search field is implemented, where you can enter a date and the app will generate a search for the whole day.

Scenario

We have a NSTextField with a NSDateFormatter. This NSTextField is hooked up by a EOControlAssociation to a EODisplayGroup via the EOControlAssociations "value" aspect. Let's assume the key for the value binding of the EOControlAssociation is "ourDate".

The EODisplayGroup is inQueryMode. The EODisplayGroup has a EODatabaseDataSource, which will be used to fetch objects from a specific EOEntity, known to the EODatabaseDataSource.

If we enter a date in the NSTextField, the NSDateFormatter will create an NSCalendarDate and store this in the "Query Dictionaryies" of the EODisplayGroup. If then the EODisplayGroup is asked to fetch, this date will be used to construct a fetch, which will look like this:

   SELECT ID_R, OUR_DATE from OUR_ENTITY where OUR_DATE = '1.1.2009 12:00.00'

This scenario is the same across standard EOF and MulleEOF, but now we use MulleEOF specifica to improve this.

Todo

We'd prefer to not match the date exactly, but actually match the range of a day. So the produced SQL should rather look like this:

   SELECT ID_R, OUR_DATE from OUR_ENTITY where OUR_DATE >= '1.1.2009 00:00.00' && OUR_DATE < '2.1.2009 00:00:00'

Solution

Create a sameDayAs: method for NSCalendarDate, that checks if two NSCalendarDates are on the same day.


<source lang="objc">@interface NSCalendarDate ( SameDay)

- (BOOL) sameDayAs:(NSCalendarDate *) other;

@end


@implementation NSCalendarDate ( SameDay)

- (BOOL) sameDayAs:(NSCalendarDate *) other {

  return( [other dayOfMonth] == [self dayOfMonth] &&
          [other monthOfYear] == [self monthOfYear] &&
          [other yearOfCommonEra] == [self yearOfCommonEra]);

}

@end </source>


Create a subclass of EOKeyValueQualifier to hook the sameDay: functionality into EOControl and EOAccess. Subclassing from EOKeyValueQualifier is convenient, because amongst other methods schemaBasedQualifierWithRootEntity: can be inherited. Writing this can be a little bit involved.


<source lang="objc">@interface SameDayAsQualifier : EOKeyValueQualifier { } @end


@implementation SameDayAsQualifier

// // With the load, we make sameDayAs parseable as an operator allowing: // // q = [EOQualifier qualifierWithQualifierFormat:@"ourDate sameDayAs %@", date]; // + (void) load {

  [EOQualifier addOperator:@selector( sameDayAs:)
                withString:@"sameDayAs"
              isRelational:NO
            qualifierClass:[SameDayQualifier class]];   

}


// // Produces SQL by decomposing the SameDayAsQualifier into an EOAndQualifier // with subqualifiers and letting SQL be generated for them // - (NSString *) sqlStringForSQLExpression:(EOSQLExpression *) sqlExpression {

  EOQualifier   *q;
  NSString      *s;
  q = [self decomposedQualifier];
  s = [q sqlStringForSQLExpression:sqlExpression];
  return( s);

}


// // transform into basic EOQualifier subclasses // - (EOQualifier *) decomposedQualifier {

  NSCalendarDate   *date1;
  NSCalendarDate   *date2;
  EOQualifier      *;
  EOQualifier      *q1;
  EOQualifier      *q2;
  id               value;
  NSString         *key;
  key   = [self key];
  value = [self value];
  date1 = [value dateByAddingYears:0
                            months:0
                              days:0
                             hours:-[value hourOfDay]
                           minutes:-[value minuteOfHour]
                           seconds:-[value secondOfMinute]];
  date2 = [date1 dateByAddingYears:0
                            months:0
                              days:1
                             hours:0
                           minutes:0
                           seconds:0];
  q1 = [[EOKeyValueQualifier alloc] initWithKey:key
                               operatorSelector:EOQualifierOperatorGreaterThanOrEqualTo
                                          value:date1];
  q2 = [[EOKeyValueQualifier alloc] initWithKey:key
                               operatorSelector:EOQualifierOperatorLessThan
                                          value:date2];
  
  q = [EOAndQualifier qualifierWithQualifiers:q1, q2, nil];
  [q1 release];
  [q2 release];
  return( q);

}


// // or inherit this also for free from EOKeyValueQualifier // - (BOOL) evaluateWithObject:(id) obj {

  id   value;
  
  value = [obj valueForKeyPath:[self key]];
  return( [[[self value] sameDayAs:value]);

}

@end

</source>


We modify the EOControlAssocation, so that it uses "@query_sameDayAs.ourDate" instead of "ourDate" as the key for its value aspect.

How it works

When the user inputs a date string into the NSTextField, the NSFormatter converts it into a NSCalendarDate. The EOAssociation read this value and stores it into the EODisplayGroup's Query Dictionaries along with the key. Since the key is prefixed with "@query_sameDayAs", the EODisplayGroup recognizes sameDayAs: as the query operator and memorizes this. It will remove the prefix "@query_sameDayAs." from the key and store the attribute key "ourDate" along with the value.

When the user affects the fetch from the EODisplayGroup, the EODisplayGroup will take the attribute key, the operator and the value from the query dictionaries to construct the EOQualifier. As the SameDayAsQualifier has been registered for this specific operator during +load, it will be used for construction.

This SameDayAsQualifier is then given to the EODatabaseDataSource which will use it to fetch from the database. During this process, the -sqlStringForSQLExpression: method of the SameDayAsQualifier will be used to produce the proper SQL.