You are browsing the archive for Xcode.

Avatar of marc

by marc

It’s Podcast Time!

July 5, 2012 in Cocoa, Data Abstract, iOS, Mac, non-tech, Oxygene, Podcast, Relativity, Xcode

i know it’s been a while since we have put out an episode of RemObjects Radio, and we apologize for that.

In the meantime however, there have been some other podcast events that might interest you:

  • Yesterday, i had the honor of being invited to join Scotty and John of iDeveloper TV for Episode 58 of their podcast, aptly entitled “More than Two Tiers”.

    We ended up spending pretty much the entire hour of the show talking about Data Abstract, Relativity Server and general technical concepts, challenges and ideas related to multi-tier database access from the perspective of Mac and iOS developers.

    You can find the episode to download and listen to here, although i really recommend subscribing to the entire podcast series, as John and Scotty just do an awesome job of providing information for Mac and iOS developers week after week (and RemObjects Software is proud to have been supporting the show thru sponsorship from day one).

  • Last month, Jim was on the IMI tech Talk Podcast and talked about Augmented Reality, Google Glass and more. Have a listen, Jim’s segment starts around 12 minutes into the episode and goes on thru the end (note that unfortunately the .mp3 link on that page seems to be wrong; the direct link to the right mp3 is here).
  • Finally, the Podcast @ Delphi.org turned the tables in late May, making regular host Jim McKeeth the interviewee in honor of the 50th episode of Delphi developer’s favorite podcast. While the episode touches on many topics, Jim, Jamie and Stuart spend a good deal of the time discussing RemObjects-related topics, including the Oxygene language and compiler, our cross-platform development philosophy, and a lot more. Go check it out here.
Avatar of Alex

by Alex

Showing hierarchical data in the NSOutlineView

June 29, 2012 in Data Abstract, Mac, Xcode

Today I want to talk about representing hierarchical data in a Mac application.

Cocoa offers the nice UI control NSOutlineView for this.
As the hierarchical data we can use a Data Abstract Schema instance which can contain various tables, fields, parameters, statements and many other objects.

Getting the DASchema

Retrieving the DASchema object from a Data Abstract server is quite simple:

NSString *serviceName = @"DASampleService";
NSString *urlString = @"http://MyServer:8099/bin";
NSURL *url = [NSURL URLWithString:urlString];
 
DARemoteDataAdapter *rda = [[DARemoteDataAdapter alloc] initWithTargetURL:url];
[[rda dataService] setServiceName:serviceName];
 
[rda beginGetSchemaWithBlock:^(DASchema *result) {
    [self setSchema:result];
}];

Defining our tree structure

The first thing we need to do is specify our tree-like hierarchy.
In order to simplify the sample, we will not implement the complete DASchema structure, only parts of it. The root object will be presented by the schema object with the tables and commands collection as children. Each table has a collection of fields as children.

So in addition to the existing DASchema elements, we need to declare some virtual elements in our tree. The schema, for example, has a collection of tables and commands. To avoid them being messed up, we want to have the collections separately, each in its own subfolder. The same is true for the fields collection inside each schema table.

These virtual classes should expose their name, note, icon and collection of children. All this info will be used by NSOutlineView for building the tree.

 
//------------------------------------------------------
//
//  OutlineNode.h
//
 
#import <DataAbstract/DASchema.h>
 
@interface DASchemaItem : NSObject
-(NSString *)name;
-(NSString *)note;
-(BOOL)isLeaf;
-(NSImage *)image;
-(NSArray *)children;
@end
 
@interface DASchemaCollection : DASchemaItem {
  DASchemaBaseObject *parent;
}
-(id)initWithParent:(DASchemaBaseObject *)aParent;
@end
 
@interface DASchemaDataTableCollection : DASchemaCollection
@end
 
@interface DASchemaDataTableFieldCollection : DASchemaCollection
@end
 
@interface DASchemaCommandCollection : DASchemaCollection
@end
 
//------------------------------------------------------
//
//  OutlineNode.m
//
#import "OutlineNode.h"
 
@implementation DASchemaItem
 
-(BOOL)isLeaf {
  return NO;
}
 
-(NSImage *)image {
  return nil;
}
 
-(NSArray *)children {
  return nil;
}
 
-(NSString *)name {
  return @"<collection>";
}
 
-(NSString *)note {
  return @"<virtual element>";
}
 
@end
 
@implementation DASchemaCollection
 
-(id)initWithParent:(DASchemaBaseObject *)aParent {
  self = [super init];
  if (self != nil) {
    parent = (DASchemaBaseObject *)aParent;
  }
  return self;
}
 
- (void) dealloc {
  [super dealloc];
}
 
-(NSImage *)image {
  return [NSImage imageNamed:@"Collection.png"];
}
 
@end
 
@implementation DASchemaDataTableCollection
 
-(NSArray *)children {
  return [(DASchema *)parent tables];
}
 
-(NSString *)name {
  return [NSString stringWithFormat:@"TABLES (%ld)",
           [[(DASchema *)parent tables] count]];
}
 
@end
 
@implementation DASchemaDataTableFieldCollection
 
-(NSArray *)children {
  return [(DASchemaDataTable *)parent fields];
}
 
-(NSString *)name {
  return [NSString stringWithFormat:@"Fields (%ld)",
          [[(DASchemaDataTable *)parent fields] count]];
}
 
@end
 
@implementation DASchemaCommandCollection
 
-(NSArray *)children {
	return [(DASchema *)parent commands];
}
 
-(NSString *)name {
	return [NSString stringWithFormat:@"COMMANDS (%ld)",
            [[(DASchema *)parent commands] count]];
}

We then need to extend some DASchema classes with the same generic methods as above (which return information about children and icons). Fortunately, Objective-C allows to do that quite easily with help of Categories.

 
// Append it to OutlineNode.h
 
@interface DASchemaBaseObject (OutlineNode)
-(NSArray *)children;
-(BOOL)isLeaf;
@end
 
//------------------------------------------------------
// Append it to OutlineNode.m
 
@implementation DASchemaBaseObject (OutlineItem) 
 
-(NSImage *)image {    
  return nil;
}
 
-(NSArray *)children {
   return nil;
}
 
-(BOOL)isLeaf { 
  return [[self children] count] < 1;
}
 
@end
 
@implementation DASchema (OutlineItem) 
 
-(NSArray *)children {  
  DASchemaCollection *tables = 
    [[[DASchemaDataTableCollection alloc] initWithParent:self] autorelease];
  DASchemaCollection *commands = 
    [[[DASchemaCommandCollection alloc] initWithParent:self] autorelease];
  NSArray *result = [NSArray arrayWithObjects:tables, commands, nil];
  return result;
}
 
@end
 
@implementation DASchemaDataTable (OutlineItem)
 
-(NSArray *)children {    
  DASchemaCollection *fields = 
    [[[DASchemaDataTableFieldCollection alloc] initWithParent:self] autorelease];
  NSArray *result = [NSArray arrayWithObjects:fields, nil];
  return result;
}
 
-(NSImage *)image {    
  return [NSImage imageNamed:@"Table.png"];
}
 
@end
 
@implementation DASchemaDataTableField (OutlineItem)
 
-(NSArray *)children {  
    return nil;
}
 
-(NSImage *)image {
  if (self.isPrimaryKey)
    return [NSImage imageNamed:@"KeyField.png"];
  return [NSImage imageNamed:@"RegularField.png"];
}
 
@end
 
@implementation DASchemaCommand (OutlineItem)
 
-(NSArray *)children {
  return nil;
}
 
-(NSImage *)image {  
  return [NSImage imageNamed:@"Command.png"];
}
 
@end

Now we are ready to bind our schema object to NSOutlineView. I prefer to do that using the Cocoa Bindings approach. It requires almost no coding and the whole job can be done in the Interface builder that loads in the working area when we select the xib file.

Binding NSOutlineView

As the data source for the NSOutlineView, we can use the NSTreeController class. You can locate it in the Object Library, then drag and drop it to the document designer.

After that, select the recently added tree controller object and go to the Utilities view Attribute Inspector . In the Tree Controller section, we need to set the key paths for obtaining the children collection and for defining if the tree node is a leaf or can be expanded.

Also in the Object Controller section, we need to set the Class as the Mode and the DASchemaBaseObject as the Class Name , then add name & note as Keys. These keys will be available for selecting later when we will bind the outline view columns to this tree controller.

Go to the Bindings Inspector and bind the tree controller to the children method of the schema property.

Now the tree controller is configured and we can bind the outline view columns to the key paths exposed by the tree controller.
Bind the SchemaItem column.

Then bind the Note column.

And that’s all. When we run the application, we will get something like this:

Note that Cocoa Bindings provides two-way binding, so we can edit tree node items just in outline view without any additional efforts.

Adding Images to the outline view items

You may notice on the screens above that NSOutlineView shows its content with images. To do that, we’ve reused the ImageAndTextCell class taken from the SourceView Sample.

We have included it to our sample project and selected it as the custom class of the outline view column cell.

We then need to declare the support of the NSOutlineViewDelegate protocol for our window controller and implement the
outlineView:willDisplayCell:forTableColumn:item: method which will be called by the outline view control before drawing its cells.

- (void)outlineView:(NSOutlineView *)outlineView
    willDisplayCell:(id)cell
     forTableColumn:(NSTableColumn *)tableColumn
               item:(id)item {
    // draw images for first column only
    if ([@"Schema Item" compare:[[tableColumn headerCell] title]] == NSOrderedSame) {
 
        NSImage *image = nil;
        if ([item respondsToSelector:@selector(image)])
            image = [item image];
        else
            if ([item isKindOfClass:[NSTreeNode class]])
                image = [[item representedObject] image];
 
        ImageAndTextCell *c = (ImageAndTextCell *)cell;
        [c setImage:image];
    }
}

The source code for the sample described in this article can be downloaded from here:
OutlineViewSample.zip

Thanks for your attention.

Avatar of Alex

by Alex

Using Remote Commands with Data Abstract for Xcode

June 5, 2012 in Data Abstract, iOS, Mac, Xcode

Recently, we have added some new functionality to the DARemoteDataAdapter for working with Data Abstract Commands.

Let me briefly refresh your memory about what Commands are and when they can be helpful.

Data Abstract Commands are part of the DASchema and represent a reference to certain Database Stored Procedures or just keep custom SQL expressions we need to execute.

The most common commands perform INSERT/UPDATE/DELETE actions for given tables.
In addition, they can be used to perform calculations in the database and return the results to the user.

Before now, we could only perform command calls by using the DataAbstractService_Proxy directly.
I’ve posted the workaround for that some days ago Support for RemoteCommands in DA/Xcode?.

But with the next release it will be much simpler.

To execute commands from the sample mentioned above, we just have to perform the following code:

[remoteDataAdapter executeCommand:@"Insert_BPWorkers" 
      withParameterValuesAndNames:@"John1",        @"FirstName",
                                  @"Doe1",         @"LastName",
                                  @"444-22-33-22", @"Phone",  
                                  [NSDate date],   @"BirthDate",
                                  @"Manager",      @"Position", nil];

The same can easily be done in an asynchronous way.

But let me show you how it works on a more complex sample with stored procedures that have output parameters.
Imagine you have an MSSQL database with the following simple TestStoredProcedure:

CREATE PROCEDURE [dbo].[TestStoredProcedure] 
  @groupId INT = 0, 
  @name nvarchar(255) OUTPUT,
  @COUNT INT OUTPUT
AS
BEGIN
  SET NOCOUNT ON;
  SELECT TOP 1 @name = p.Name FROM Products p WHERE p.GroupId = @groupId;
  SELECT @COUNT = COUNT(*) FROM Products p WHERE p.GroupId = @groupId;
  RETURN 157;
END

As you have noticed, this stored procedure returns @name & @count as output parameters and some “magic number” as the result. So let’s use it.

Note
Don't forget that Data Abstract doesn't work with the database objects directly, 
but with schema objects (tables, commands etc).

If you need to get back the output parameters, you need to use the ExecuteCommandEx method.

Beside the usual arguments like command name and parameters, it takes another NSDictionary as the output argument which will contain the output values.

The code for the asynchronous call will look something like this:

 
-(IBAction)callTheCommandEx:(id)sender {
  NSString *COMMAND_NAME = @"TestStoredProcedure";
  NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:10], @"groupId", nil];     
  DAAsyncRequest *request = [dataAdapter beginExecuteCommandEx:@"TestStoredProcedure"
                                                withParameters:params 
                                                         start:NO];
  [request setDelegate:self];
  [request start];
}
 
     - (void)asyncRequest:(DAAsyncRequest *)request 
didFinishExecutingCommand:(NSString *)commandName 
               withResult:(int)result 
          andOutputParams:(NSDictionary *)outParams
{
    NSLog(@"Called ExecuteCommandEx for %@ with result %i", commandName, result);
    NSLog(@"%@", outParams);
}

Or the same but using blocks:

-(IBAction)callTheCommandEx:(id)sender {
  [dataAdapter beginExecuteCommandEx:@"TestStoredProcedure"
                      withParameters:params 
                           withBlock:^(int result, NSDictionary *outParams) {
                                        NSLog(@"Called ExecuteCommandEx for %@ with result %i", @"TestStoredProcedure", result);
                                        NSLog(@"%@", outParams);
                                     }];

In both cases we’ve got the following results:

Called ExecuteCommandEx for TestStoredProcedure with result -1
{
    "RETURN_VALUE" = 157;
    count = 42;
    name = "APC ProtectNet PNET1GB";
}

Developing Database Applications for Mac and iOS (UPDATE: 6 chapters free)

February 24, 2012 in Books, Data Abstract, iOS, Mac, Xcode

Developing Database Applications for Mac and iOS is a book about Data Abstract for Xcode that we started work on in 2011. Writing has been put on hold for a bit, but we are reviving the project and are planning to complete the book some time in 2012.

In the mean time, select portions of some chapters that we think are ready for consumption are being integrated here on the wiki for your benefit, while the remainder of the book is still being worked on.

The following topics are currently available:

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 10

Chapter 12

Creating an Interactive Widget for iBooks without Dashcode – Part 2

February 21, 2012 in Data Abstract, iOS, JavaScript, Xcode

A quick update to my post from last week. Xcode became available as an App in the Mac App Store, and it no longer includes Dashcode by default. That means you need to download Dashcode separately, which requires an Apple Developer account. Apple gave the impression that Dashcode was included with iBooks Author, which doesn’t appear to be the case. If you don’t have Dashcode, don’t worry, you won’t need it for today.

This is part 2 of my series on Interactive Widgets for iBooks Author.

  1. Using DashCode to create an interactive widget
  2. Creating an interactive widget without Dashcode (This article)
  3. Creating a RemObjects SDK client widget
  4. Creating a Data Abstract client widget

If you already have an existing HTML5 & JavaScript “applet”, it is the best solution to use, since you can easily adapt it to work as an interactive widget for iBooks. Read the rest of this entry →

Creating Interactive Widgets for iBooks Author and the iPad – Part 1

February 16, 2012 in Data Abstract, iOS, JavaScript, Xcode

When Apple unveiled iBooks2 with support for advanced textbooks on the iPad, they also released iBooks Author to make the creation of such advanced textbooks easier. One of the features of iBooks Author is the ability to embed interactive HTML5 & JavaScript widgets created with Dashcode. Unfortunately, there isn’t a lot of details on exactly how to do that.

Since we just released our RemObjects SDK and Data Abstract for JavaScript, I was naturally curious if they could be used to build interactive widgets for iBooks2. This would be a really powerful feature – the ability to have live updated data displayed in an iBook. Since there wasn’t a whole lot of details I figured a lot of it out of the hardway. Since I started, a few details have started to emerge, but no complete references.

This is the first of four articles on creating interactive widgets for iBooks Author and the iPad’s iBooks2.

  1. Using DashCode to create an interactive widget (This article)
  2. Creating an interactive widget without Dashcode
  3. Creating a RemObjects SDK client widget
  4. Creating a Data Abstract client widget

Read the rest of this entry →

Avatar of marc

by marc

Choosing the Best Toolchain for each Platform

February 4, 2012 in .NET, Android, ASP.NET, Cocoa, Data Abstract, Delphi, iOS, Java, JavaScript, Mac, Metro, Mono, MonoTouch, non-tech, Oxygene, Platforms, Prism, RemObjects, Visual Studio, Windows, Windows Phone, Xcode

Four or five years ago, the software world was simple: if you were a commercial software developer, you were developing for Windows. But this has changed drastically with the advent of mobile platforms such as iOS and Android, the steadily increasing market share of Macs, and the establishment of new development paradigms such as rich web applications hosted in browsers.

Here at RemObjects, I field the majority of so called “sales related” emails (that is, emails that aren’t technical support requests handled by our excellent support team and the product developers) myself — and as this century gets older, I see more and more requests asking what tools and toolchains to pick for different platforms. “Can I develop for both Windows Phone 7 and Android with Oxygene and share code?” — “Which version of Data Abstract is best for targeting iOS?” — “Should I use Oxygene for .NET with MonoDroid or Oxygene for Java for my Android app?”.

There are a lot of great development tool chains for the different platforms out there — be they Xcode/Cocoa, Visual Studio/.NET, Delphi, JavaScript — and sometimes the decision does not seem easy, because here at RemObjects we provide editions of most of our products for various development platforms, and often more than one choice can be applied to a specific platform need.

To help make heads and tails of this, we created a small graphical overview chart on our website that gives insight into which language/framework/product combinations we support on any given platform and — more importantly IMHO — which combination we recommend.*

[*And that recommendation might not always be what you think — for example, I often get looked at weirdly when I recommend Xcode over our own Oxygene (plus Mono) for Mac development. But development tools, even versatile ones such as Oxygene, or Delphi, or Visual Studio, are not and should not be Jacks of all Trades, and thus no single tool, no matter how great (and we happen to think Oxygene is pretty great ;) can be the best choice for all scenarios. We acknowledge and honor that.]

You can find this overview matrix — which will be ever-expanding as time goes by and the development world around us changes — at remobjects.com/products/toolchains; i’m also including a static screenshot of it, below.

May it help you to choose the best set of tools for your needs!



Avatar of Alex

by Alex

Updating Data in iOS Client Applications

October 26, 2011 in Cocoa, Data Abstract, iOS, Mac, Relativity, Uncategorized, Xcode

As promised earlier, I’m going to tell you how to change data in an iOS application and apply changes to the DA Server. As client application we will use the iOS Relativity Client we have previously created.

In the course of this session we will try to implement an editor for changing data from the Orders table. We will also use UIPickerView & UIDatePicker for changing the Order’s fields.
The source code can be obtained from here.
Let’s start!

The first thing we need to do is to add a new editor view and its controller.
After performing File->New…->New File, select the “UIViewController subclass” item. As the base class we will use the UITableViewController.
Now we need to add an edit button to the existing OrdersViewController. Clicking on this button should open an editor for the selected row.

- (void)viewDidLoad {
    [super viewDidLoad];
 
    // create & set Edit button    
    UIBarButtonItem *actionButton = 
     [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit 
                                                   target:self 
                                                   action:@selector(edit:)];
    [[self navigationItem] setRightBarButtonItem:actionButton];
    [actionButton release];
    ...
}
 
- (IBAction)edit:(id)aSender {
 
    OrderEditorController *editor = 
      [[[OrderEditorController alloc] initWithStyle:UITableViewStyleGrouped] autorelease];
 
    // set OrderViewController as the delegate to be able to notify it about the results of the editing process
    [editor setDelegate:self];
 
    // find the currently selected row
    NSIndexPath *path = [self.tableView indexPathForSelectedRow];
    NSInteger idx = [path indexAtPosition:1];
    DADataTableRow *row = [orderRows objectAtIndex:idx];
 
    // and set it for our editor 
    [editor setOrderRow:row];
 
    // push the editor view to be a topmost view
    [[self navigationController] pushViewController:editor animated:YES];
}
 
// delegate the method that the editor will call for applying changes
-(void)endEditWithOk:(DADataTableRow *)row {
    // applying changes on Relativity server
    [[DataAccess sharedInstance] applyUpdatesForTable:[row table]];
    // reload view for synchronizing client-side data
    [[self tableView] reloadData];
}
 
// delegate the method that the editor will call for canceling changes
-(void)endEditWithCancel:(DADataTableRow *)row {
    // cancel the change
    [row cancel];
    // reload table
    [[self tableView] reloadData];
}

Now it is time to start implementing our OrderEditorController. The Orders table has several fields we can edit: OrderDate and OrderStatus, but we can also change the client of the order, though I don’t think we need to change the document in such a radical way ;) .

Note: There is one small problem we need to resolve first. Unfortunately, the current version of the PCTrade database doesn’t have an OrderStatus reference, so the Orders table only has its integer values (which by default are 0).
Let’s assume that we need to implement several available document statuses. A new uncompleted order usually has the “New” status. When it is completed but not payed yet, it has a “Pending” status. When we received the money, it changes to “Closed” and finally, there is the additional “Deleted” status. In the future we probably may want to track the document history, so we loose any of the orders, even if they were deleted. So our database will keep those orders, but with the “Deleted” status. In order to keep the correspondence between the available status numbers and their names, we will add a new NSArray into our DataAccess class as shown below.

/////////////////////////////
//  DataAccess.h
@interface DataAccess : NSObject <DARemoteDataAdapterDelegate, DAAsyncRequestDelegate>
{
    ...
    NSArray *orderStatusNames;
}
...
@property (readonly) NSArray *orderStatusNames;
-(NSUInteger)idForOrderStatusName:(NSString *)name;
-(NSString *)nameForOrderStatus:(NSUInteger)status;
@end
 
/////////////////////////////
//  DataAccess.m
 
- (id) init {
	self = [super init];
	if (self) {
		...
 
        orderStatusNames = [[NSArray alloc] initWithObjects:
                         @"New", 
                         @"Pending", 
                         @"Closed", 
                         @"Deleted", 
                         nil];
 
	}
	return self;
}
 
- (void) dealloc {
   ...  
   [orderStatusNames release], orderStatusNames = nil;
   [super dealloc];
}
 
 
-(NSString *)nameForOrderStatus:(NSUInteger)status {
 
    if (status >= [orderStatusNames count])
        return @"Unknown state";
 
    return [orderStatusNames objectAtIndex:status];
}
 
-(NSUInteger)idForOrderStatusName:(NSString *)name { 
 
    for (NSUInteger i = 0; i < [orderStatusNames count]; i++) {
        NSString *statusName = [orderStatusNames objectAtIndex:i];
        if ([statusName compare:name] == NSOrderedSame) 
            return i;
    }
    return 0;
}
 
-(NSArray *)orderStatusNames {
 
    return orderStatusNames;
}

Now we are ready to start the editor implementation. Please open the OrderEditorController.h file and add the following:

@interface OrderEditorController : UITableViewController {
 
    DADataTableRow *orderRow;
 
    UIBarButtonItem *saveButton;
    UIBarButtonItem *cancelButton;
 
    id delegate;
    NSDateFormatter *df; 
}
 
@property (retain) DADataTableRow *orderRow;
@property (assign) id delegate;
 
- (IBAction)save:(id)aSender;
- (IBAction)cancel:(id)aSender;
@end

Our editor should have references to the edited row, the delegate reference, the save & cancel buttons and the appropriate methods. This will implement them:

#pragma mark - View lifecycle
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    // create & add cancel button    
    cancelButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" 
                                                    style:UIBarButtonItemStylePlain 
                                                   target:self action:@selector(cancel:)];
    self.navigationItem.leftBarButtonItem = cancelButton;
 
    // create & add save button    
    saveButton = [[UIBarButtonItem alloc] initWithTitle:@"Save" 
                                                  style:UIBarButtonItemStyleDone 
                                                 target:self action:@selector(save:)];
    self.navigationItem.rightBarButtonItem = saveButton;
 
    // we will also need this formatter for converting NSDate to NSString 
    df = [[NSDateFormatter alloc] init];
    [df setDateStyle:NSDateFormatterMediumStyle];
}
 
- (IBAction)save:(id)aSender {
 
    if ([delegate respondsToSelector:@selector(endEditWithOk:)])
        [delegate endEditWithOk:orderRow];
 
    [self.navigationController popViewControllerAnimated:YES];
 
}
 
- (IBAction)cancel:(id)aSender {
 
    if ([delegate respondsToSelector:@selector(endEditWithCancel:)])
        [delegate endEditWithCancel:orderRow];
 
    [self.navigationController popViewControllerAnimated:YES];
}

In the majority of cases, we can represent the editor view as the table view, where each cell represents the field name at the left side and the current value at the right side. We can also group certain fields together based on general meaning or application logic. To do that we have to initialize our OrderEditorController with the UITableViewStyleGrouped style. When the user taps on a certain cell, we will need to allow him to edit that value.

So now we need to configure our table view as shown below:

 
#pragma mark - Table view data source
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // we want to have two groups, 
    // one for OrderDate & OrderStatus, and one for the Client field 
    return 2;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    switch (section) {
        case 0:
            // the first section will have two cells (showing OrderDate & OrderStatus)
            return 2;
        case 1:
            // the second section will have a single cell (showing the Client field)
            return 1;
 
        default:
            return 0;
    }
}
 
static NSString *TABLE_CELL_ID = @"OrderEditorCell";
 
- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TABLE_CELL_ID];
    if (cell == nil) {
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 
                                     reuseIdentifier:TABLE_CELL_ID] autorelease];
    }
 
    // Configure the cell...
    int section = [indexPath section];
    int index = [indexPath indexAtPosition:1];
    switch (section) {
      case 0: { // for the first section ...
        switch (index) {
          case 0: { // configure the cell to show the OrderDate field
                    [[cell textLabel] setText:@"Order Date:"];
                    NSDate *value = [orderRow valueForKey:@"OrderDate"];
                    NSString *valueAsString = value ? [df stringFromDate:value] : @"Click to select";
                    [[cell detailTextLabel] setText:valueAsString];
                    break;
          }
          case 1: { // configure the cell to show the OrderStatus field
                    [[cell textLabel] setText:@"Order Status:"];
                    NSNumber *value = [orderRow valueForKey:@"OrderStatus"];
                    NSString *valueAsString = value ? 
                         [[DataAccess sharedInstance] nameForOrderStatus:[value intValue]] : 
                         @"Click to select";
                    [[cell detailTextLabel] setText:valueAsString];
                    break;
          }
        }
        break;
      }
      case 1: { // for the second section
        switch (index) {
          case 0: { // configure the cell to show the Client field
                    [[cell textLabel] setText:@"Client:"];
                    NSString *value = [orderRow valueForKey:@"CustomerName"];
                    NSString *valueAsString = value ? value : @"Click to select";
                    [[cell detailTextLabel] setText:valueAsString];
                    break;
          }
        }
        break;
     }
     default:
       break;
    }
    [cell setNeedsDisplay];
	return cell;
}

Now, when you compile and run the project, you should see something like the following:

Next we can start to implement the editing for the OrderDate and OrderStatus fields. For editing dates we can use the really good NSDatePicker control with the appropriate configuration. For editing the OrderStatus we can use our custom picker populated with available statuses.
I also want the picker view to appear at the bottom of the current editor using animation, like the standard Contacts application does. Having spent some time with Google, I’ve found an interesting Data Cell sample with the required functionality. I’ve just slightly adjusted it for our purposes, namely to support animation for custom pickers.

- (void)viewDidLoad {
 
  ...
  // create & configure our DatePicker
  datePickerView = [[UIDatePicker alloc] init];
  [datePickerView setDatePickerMode:UIDatePickerModeDate];
  [datePickerView addTarget:self 
                     action:@selector(pickerDidChange) 
           forControlEvents:UIControlEventValueChanged];
 
  pickerDoneButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" 
                                                      style:UIBarButtonItemStyleDone 
                                                     target:self action:@selector(pickerDone)];
  pickerCancelButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel" 
                                                        style:UIBarButtonItemStyleDone 
                                                       target:self action:@selector(pickerCancel)];
}
 
#pragma mark - Common picker routines
-(void)showPicker:(UIPickerView *)picker {
 
    if (!picker)
        return;
 
    if (activePicker) 
        [self hidePicker:activePicker];
 
    activePicker = picker;
 
    if (picker.superview == nil) {
        [self.view.window addSubview:picker];
 
        // size up the picker view to our screen and compute the start/end 
        // frame the origin for our slide-up animation
        // compute the start frame
        CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
        CGSize pickerSize = [picker sizeThatFits:CGSizeZero];
        CGRect startRect = CGRectMake(0.0,
                                      screenRect.origin.y + screenRect.size.height,
                                      pickerSize.width, pickerSize.height);
        picker.frame = startRect;
 
        // compute the end frame
        CGRect pickerRect = CGRectMake(0.0,
                                       screenRect.origin.y + screenRect.size.height - pickerSize.height,
                                       pickerSize.width,
                                       pickerSize.height);
        // start the slide-up animation
        [UIView beginAnimations:nil context:NULL];
        [UIView setAnimationDuration:0.3];
 
        // we need to perform some post operations after the animation is complete
        [UIView setAnimationDelegate:self];
 
        picker.frame = pickerRect;
 
        // shrink the table's vertical size to make room for the date picker
        CGRect newFrame = self.tableView.frame;
        newFrame.size.height -= picker.frame.size.height;
        self.tableView.frame = newFrame;
        [UIView commitAnimations];
 
        // add the "Done" & "Cancel" buttons to the nav bar
        self.navigationItem.rightBarButtonItem = pickerDoneButton;
        self.navigationItem.leftBarButtonItem = pickerCancelButton;
    }
}
 
-(void)removePicker:(UIPickerView *)picker {
 
    if (!picker)
        return;
 
    if (picker.superview != nil) {
        [picker removeFromSuperview];    
    }
}
 
-(void)hidePicker:(UIPickerView *)picker {
 
    if (!picker)
        return;
 
    activePicker = nil;
 
    CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
    CGRect endFrame = picker.frame;
    endFrame.origin.y = screenRect.origin.y + screenRect.size.height;
 
    // start the slide-down animation
    [UIView beginAnimations:nil context:picker];
    [UIView setAnimationDuration:0.3];
 
    // we need to perform some post operations after the animation is complete
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
 
    picker.frame = endFrame;
    [UIView commitAnimations];
 
    // grow the table back again in vertical size to make room for the date picker
    CGRect newFrame = self.tableView.frame;
    newFrame.size.height += picker.frame.size.height;
    self.tableView.frame = newFrame;
 
    // remove the "Done" button and restore the nav bar
    self.navigationItem.rightBarButtonItem = saveButton;
    self.navigationItem.leftBarButtonItem = cancelButton;
 
    // deselect the current table row
    //NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
    //[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
 
}
 
-(void)animationDidStop:(NSString *)animationID 
               finished:(NSNumber *)finished 
                context:(void *)context {
    [self removePicker:context];
}
 
// called when we click the Done button
-(void)pickerDone {
 
    // change the data
    if (activePicker == (UIPickerView *)datePickerView)
        [orderRow setValue:datePickerView.date forKey:@"OrderDate"];
 
    // hide picker
    [self hidePicker:activePicker];
    // reload table view to show changes
    [[self tableView] reloadData];
}
 
// called when we click on the Done button
-(void)pickerCancel {
    // just hide the picker
    [self hidePicker: activePicker];
    [[self tableView] reloadData];
}
 
// the following method will sync the current picker value with the value in the table view
- (void)pickerDidChange {
 
    NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
 
    if (activePicker == (UIPickerView *)datePickerView)
        cell.detailTextLabel.text = [df stringFromDate:datePickerView.date];
}

Now we need to launch the UIDatePicker when a user taps on the OrderDate field:

- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
    [self hidePicker:activePicker];
    int section = [indexPath section];
    int index = [indexPath indexAtPosition:1];
 
    switch (section) {
      case 0: {
        switch (index) {
          case 0: {
            datePickerView.date = [orderRow valueForKey:@"OrderDate"];
            [self showPicker:(UIPickerView *)datePickerView];
            break;
          }
          case 1: {
 
            break;
	  }
        }
      }
    }
}

When you compile and run the application, you should get something like the following:

Finally, we need to implement our custom UIPickerView, which is quite simple. At the beginning we need to create a custom UIPickerView and set its data source and delegate properties:

- (void)viewDidLoad {
    ...    
    statePickerView = [[UIPickerView alloc] init];
    [statePickerView setDataSource:self];
    [statePickerView setDelegate:self];
    [statePickerView setShowsSelectionIndicator:YES];
}

Then we need to add a support UIPickerViewDataSource protocol to our editor class and implement several methods, as shown below:

 
#pragma mark - Custom Picker routines
 
// callback method for filling content for our custom statuses picker
- (NSString *)pickerView:(UIPickerView *)pickerView 
             titleForRow:(NSInteger)row 
            forComponent:(NSInteger)component {
 
    if (pickerView == statePickerView)
        return [[DataAccess sharedInstance] nameForOrderStatus:row];
    return @"";
}
 
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
 
    // it should be a simple picker with a single component
    if (pickerView == statePickerView)
        return 1;
    return 0;
}
 
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
 
    if (pickerView == statePickerView)
        return [[[DataAccess sharedInstance] orderStatusNames] count];
    return 0;
}

Change the tableView:didSelectRowAtIndexPath: method to show our state picker:

#pragma mark - Table view delegate
 
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
   ...    
    switch (section) {
      case 0: {
        switch (index) {
          case 0: {
            ...
          }
          case 1: {
            [self showPicker:statePickerView];
            break;
          }
        }
      }
    }
}

That’s all. Now when you tap on the status cell of the editor, you should get something like this:

You can choose a new status, then click the done button and finally save the current order.

I’m slightly worried that you may find this blog post a little confusing and not easy to reproduce. We wrote quite a lot of code here, and I deliberately skipped some minor details, trying to make it easier, but you can download the whole source code from here and play with the project yourself.

Thank you for your attention ;)

Avatar of marc

by marc

RemObjects SDK and Data Abstract for JavaScript

October 7, 2011 in .NET, Data Abstract, Delphi, JavaScript, Prism, Visual Studio, Xcode

Today we are shipping the first beta builds of RemObjects SDK and Data Abstract with our new JavaScript client support. This beta is available at beta.remobjects.com for all users with an active subscription, and we are planning to ship this feature officially in the upcoming Winter release next month.

This blog post aims at giving you a quick overview into what’s there and how to use it.

RemObjects SDK and Data Abstract for JavaScript is essentially the fourth client platform for RO/DA that we have implemented, and constitutes a fully native JavaScript implementation of our RO/DA client library – with everything you need to talk to RO or DA servers directly from JavaScript-based clients. The main focus right now is on web clients (i.e. clients running locally in a browser), but RODA/JS should also be usable from any other JavaScript based platform, such as Windows 8′s Metro, cough WebOS cough, and the like.

RODA/JS is not a separate product; rather, it ships in the box with all three of our existing server platforms: RODA/.NET, RODA/Delphi and Relativity Server.

In most cases, you will want to add the ability to expose a scripting client right from inside the regular RO/DA server you are building, and we have created components that make this really easy: Both RODA/.NET and RODA/Delphi have gained (TRO)JavaScriptHttpDispatcher and (TDA)JavaScriptHttpDispatcher components that you can simply drop into your project and hook up to your server channels. These components embed and automatically serve the core RODA/JS script files, and can also serve any custom files (scripts and HTML/CSS/Images) that you created to implement your JavaScript client (from a folder or from resources embedded in your server app).

With that done, you can simply point your browser to a subpath of your server’s URL, such as http://localhost:8099/js to access your web app.

TROJavaScriptHttpDispatcher in Delphi

The screenshot above shows the RO/Delphi MegaDemo, but a similar component is available for DA and on .NET, and all MegaDemos and DA Sample Servers have been extended to already expose sample JavaScript clients.

If you run the server and point your browser to it, it will look something like this (not about to win any design awards, but it works ;) ).

MegaDemo JavaScript Client in Action

What does the source look like? Aside from the HTML that defines what we see, there are three script files included:

<script type="text/javascript" src="RemObjectsSDK.js"></script>
<script type="text/javascript" src="MegaDemoLibrary_intf.js"></script>
<script type="text/javascript" src="script.js"></script>

The first is the core RO/JS library – automatically served by the JavaScriptHttpDispatcher component. The second is the Interface file for our server’s RODL – more on that below. Lastly, scripts.js contains the code we custom-wrote to implement our client. Let’s look at that in more detail:

First, we create our service and related components (I’m shortening the code a bit for the purpose of this post, if you look at the real sample, you’ll see it will optionally choose between JSON and BinMessage, etc):

var Service;
createService();
function createService() 
{
    var svcUrl = "http://" + document.location.host + "/bin";
    Service = new MegaDemoService(new RemObjects.SDK.HTTPClientChannel(svcUrl),
    new RemObjects.SDK.BinMessage());
}

Next, let’s look at how we call Sum():

function BeginSumMethod() 
{
    var A = document.getElementById("editA").value;
    var B = document.getElementById("editB").value;
    Service.Sum(A, B, 
                    function SuccessSumMethod(result) 
                    {
                        AddLineToLog('Result is ' + result);
                    },
                    function ErrorSumMethod(msg, ex) 
                    {
                        if (ex) {
                            AddLineToLog(" Error: " + ex);
                    } else {
                        AddLineToLog(msg.getErrorMessage());
                    });
}

As you see, JavaScript is big on asynchronicity. Instead of calling Sum() and getting back a value, Sum runs asynchronously, and we provide a callback that gets executed when the result was received (and a second callback that gets called for any errors or exceptions).

To generate Interface files for your custom RODLs (such as MegaDemoLibrary_intf.js in the sample above), both Service Builder (Windows) and rodl2objc (Mac) have been expanded with an extra option to generate .js files. Simply open your RODL and save the Intf.js, as you already know how to do for all the other languages we support:

rodl2code with JS

(As you can see, rodl2objc has also received a bit of an interface facelift ;) )

That’s RemObjects SDK for JavaScript, for talking to custom Web Services. Next, let’s look at Data Abstract.

Like with RO, making a DA server accessible from JavaScript is as easy as adding the dispatcher and (optionally) some custom script or HTML files. Here’s what the JavaScript sample for our PCTrade Sample Servers looks like (both for .NET and Delphi):

PCTrade Sample for JavaScript

On the left, there’s list of all the tables, dynamically created by looking at the schema; once a table is selected, it shows a grid on the right. For this sample, we’re using a jQuery grid, but there’s nothing that inherently ties Data Abstract to jQuery or any other JavaScript framework – you get access to the raw data via a table object and can work with it any way you please. Of course, we cannot only show data, but also maintain updates and apply them back to the server.

Let’s look at the code again:

<script src="RemObjectsSDK.js" type="text/javascript"></script>
<script src="DataAbstract.js" type="text/javascript"></script>
<script src="DataAbstract4_Intf.js" type="text/javascript"></script>
<script src="/js/jquery/jquery-1.4.4.js" type="text/javascript"></script>
<script src="/js/jquery/js/grid.locale-en.js" type="text/javascript"></script>
<script src="/js/jquery/js/jquery.jqGrid.min.js" type="text/javascript"></script>
<script src="DataAbstractjQGrid.js" type="text/javascript"></script>

In addition to the core three RO/DA files (all of which are served by default by Data Abstract’s version of the JavaScriptHttpDispatcher), we include a few jQuery files, as well as a DA-specific helper file for jQGrid that we provide for your convenience. Once again, using jQGrid is just an implementation choice for this sample (much like using QuantumGrid would be for a Delphi app).

Other than that, all the application-specific script in this sample is in the main HTML file.

For example, here is how we set up the RemoteDataAdapter:

var Channel = new RemObjects.SDK.HTTPClientChannel("http://" + window.location.host + "/JSON");
var Message = new RemObjects.SDK.JSONMessage();
var Service = new RemObjects.SDK.RemoteService(Channel, Message, "DASampleService");
var daService = new DataAbstractService(Service);
var loginService = new RemObjects.SDK.RemoteService(Channel, Message, "LoginService");
var rda = new RemObjects.DataAbstract.RemoteDataAdapter(Service, loginService, RemObjects.DataAbstract.Bin2DataStreamer);

And here is how we fetch data for a table (and show it using the jQGrid helper):

rda.getData(Tables[CurrentTable], function() {
                GridView = new RemObjects.DataAbstract
                    .Views.JQGridView(Tables[CurrentTable], "#list", "#pager");
            },
        function(msg, e) {
            if (e) {alert(e)}
            else alert(msg.getErrorMessage());
        });

If you’re familiar with RO and DA, you will already see that the classes are very similar to what you know. We have client channels, messages, remote services and a remote data adapter.

Relativity Server now also optionally exposes the required JavaScript files (including full Intf.js files for its admin API) as well as a JSON message. All you need to do to activate this is to check the Enable JavaScript Publishing option in Server Explorer:

Server Explorer for Mac

If the option is grayed out, your Relativity Server does not support JavaScript yet; if the option is not available, you need a newer version of Server Explorer for Mac.

A Word about Cross-Domain Data Access

You might be asking: why does the JavaScript need to be served by the RO/DA server? Can’t I just put it on my website? Technically, you could, but there’s a limitation on most browsers that – for security reasons – won’t just let JavaScript connect to different servers willy-nilly. So if you’re opening a website from http://mywebsite.com, the browser won’t let the JavaScript on that site send requests to http://database.mywebsite.com:8099 – unless at least some of the scripts originate from that server. So you can embed JavaScript code in your website and have it talk to your RO/DA server on a different domain or port, but you must at least reference the RemObjectsSDK.js and/or DataAbstract.js files from the RO/DA server to let the browser know that it’s ok to connect there.

This limitation might not apply when using JavaScript on other non-web platforms, for example on Metro (we haven’t tested that yet) or application platforms such as Titanium. It’s a limitation of what the browser lets you do, not a limitation of our client library.

Summary

Our new native JavaScript client libraries open up RO and DA servers to a wide variety of new client platforms, with rich browser-based apps only being one of many possibilities.

We have a good six weeks of beta cycle left before this feature ships, and we hope you’ll give the beta builds a try and let us know any feedback you have. Head over to beta.remobjects.com now to get started.

Enjoy!

Avatar of marc

by marc

Server Explorer for Mac

September 30, 2011 in Data Abstract, Mac, short, Xcode

Update: The .935 update for Server Explorer for Mac has been approved and is now available on the Mac App Store, as well.