Real Life Cappuccino Tutorial - Part 1

Posted on 08 September 2009 by Johannes Fahrenkrug. Tags: Programming Cappuccino Tutorials Cocoa
Update(09/21/09): Updated the tutorial to use the newest MapKit version (new version of MapKit in the github repository and some changes in MapController.j).


Update(09/17/09): The MapKit files were missing in the github repository. It's fixed now! Thanks to Mic Pringle for pointing it out. I'm lacking some serious git-fu!

In this tutorial we'll build a real life Cappuccino application, complete with models, views and controllers, key-value-observing, delegates, JSON, a Ruby on Rails backend and even (gasp!) Google Maps. The purpose of this application will be to add locations to a list, add geo-coordinates to that location and ultimately draw the route on a Google Map. I assume you've set up your development environment, and I also assume that you've either played around with Cappuccino or Cocoa before and I won't explain every little detail. Also, you'll need some images that you'll have to copy to your project's Resources folder. You can find them here on github. So let's get started!
Part 1 - Basic Functionality

1.1 - Skeletor and the Model
First you'll have to generate a new Application skeleton by running capp gen Locations. Change into the Locations directory and make sure it works by opening index.html in your browser. You should see a very familiar greeting.

Since Cappuccino uses the MVC pattern, we should first build the model class for the location. Fire up your favorite editor and add a file right inside the Locations directory called "Location.j". The file should look like this:
1.2 - The LocationsController and Demo Data
Next we will need some demo data to work with and we'll need a controller for the locations. Add a new file called "DemoData.j" and set this contents:

Finally we need the controller for the locations. Add "LocationsController.j" and make it look like this:

Let's see if all this works in the next step!
1.3 - Basic Layout
Next we'll test if the controller works and we'll prepare the "stage" for our UI. Open up AppController.j. You'll notice the applicationDidFinishLaunching method. I'm sure you can guess when it will be called. Make the whole file look like this:

Notice the CPShadowView: it adds(!) a shadow around an existing view. So you do not want to subclass CPShadowView in order to create a view with a shadow. Instead, you want to build your normal CPView (or a subclass of it) and then add a CPShadowView to it with setFrameForContentFrame.
At the bottom of the method, we initialize the LocationsController, load the sample data, and show 2 alerts to make sure we got the data. When you reload index.html you should see an alert saying "2" and one saying "Corner of Twelfth and Vine".

Great! It works!
1.4 - The LocationListView
How about displaying the Locations? That would be pretty nice! Add a new file called "LocationListView.j". It should look like this:

Next, we'll have to tell the AppController about our new view. We'll do that by importing it at the top of AppController.j:
@import "LocationListView.j"
and we want to keep it as an ivar, so add this between the curly braces right after "LocationsControllor locationsController;":
LocationListView    locationListView;
Then, at the bottom of applicationDidFinishLaunching, remove the two alert calls and add this instead:

When you now reload index.html, you should see our 2 demo locations and you should also be able to select them (click on them).
1.5 Google Maps
What good are the geo locations if we can't display them on a map? Let's fix that! Get my fork of Ratty's MapKit here: http://github.com/jfahrenkrug/MapKit/tree/master and put the MapKit folder inside your Locations/Frameworks directory. Then add a new file called "MapController.j". It will - you guessed it - control the map!

Now import MapController.j at the top of AppController.j and add these ivars (in the curly braces):

MapController       mapController;
MKMapView           mapView;
CPTextField         coordinatesLabel;
Let's set all of this up. Add this at the end of applicationDidFinishLaunching, but before "[theWindow orderFront:self];":

Good. Next we want the map to display the location we've selected. To do that in an elegant way, we'll have a do a few things. First our LocationsController needs to know about the LocationListView. That means we have to @import the LocationListView in the LocationsController and we need to add an ivar to the controller called "locationListView" like so:

LocationListView locationListView @accessors;
That "@accessors" bit tells Objective-J to automagically add the "locationListView" and "setLocationListView" accessor methods. Next, add this method to the LocationsController:

- (Location)selectedLocation {
 return [locations objectAtIndex:[[locationListView selectionIndexes] firstIndex]];
}
Your LocationsController should now look like this:

Back in the AppController, we have to set the LocationListView's delegate. We want the AppController to be notified when something in the LocationListView changes. To do that, replace the line that reads "//1: we'll add something later" with this:

[locationListView setDelegate:self];
[locationsController setLocationListView:locationListView];
Finally, we have to implement the delegate method that gets called when the selection of the listview changes. Add this method to the AppController:

If you now reload index.html, you should be able to click back and forth between the two demo locations and the map should jump to the corresponding spot on the globe.
1.6 - Adding and Deleting Locations
Switching back and forth between only two locations can become boring after a few hours. If it hasn't gotten boring for you yet, I'll wait here until you're done. That was a lie, I'll just keep going. Next it would be nice to add and edit locations. For that we'll need a couple of things. First we want a plus and minus sign underneath the LocationListView so we have a way to add and remove locations. Add a new file called "LocationsToolbar.j". It should look like this:

You know the deal by now: import it in AppController and add an ivar called "locationsToolbar". Add this to applicationDidFinishLaunching:

As you can see, we've set the toolbar's delegate to be the locationsController. So we need to implement the delegate methods in LocationsController.j:

If you now reload index.html, you should be able to add and remove locations (although the map will not like the empty coordinates).
1.7 - Editing Locations
Let's add another view, the "LocationDetailView.j":

Import it in AppController and add an ivar.
Add yet another controller, called "LocationDetailController.j":

Also import it in AppController and add an ivar. Now let's hook it all up in AppController. Add this to applicationDidFinishLaunching:

Also import the LocationDetailController in the LocationsController and add an ivar with the @accessors directive. Then, in the AppController, change the delegate of the locationListView from "self" to the locationsController like so:

[locationListView setDelegate:locationsController];
Next remove the "collectionViewDidChangeSelection" method from AppController and add this one to LocationsController instead:

When you now reload index.html, you should be able to edit the test locations and even see the name change in the listview as you type, thanks to KVO.
But one important part is still missing: finding locations! Let's do that next.
1.8 - Finding Locations
We need a searchfield and a button. Add this ivar to AppController:

CPTextField         searchField;
Next, add this to applicationDidFinishLaunching:

And finally add these two methods to AppController:

Now when you reload index.html, you'll be able to search, edit, add and remove locations and find them on the map.

You can find the complete source code on github.
Big thanks go out to Thomas Balthazar of the great Cappuccinocasts and the great cappuccino demo projects and the nice panorama Cappuccino tutorials. Thank you, guys!
So much for today. In Part 2, we'll add the routing window that will display your route. And in part 3 we'll add Rails support to actually load and save the locations.


Comments

Eduard Bondarenko said...

Thanks, one more notice: github repos don't have .gitmodules reference to MapKit.

No submodule mapping found in .gitmodules for path 'Frameworks/MapKit'

September 09, 2009 03:08 PM

Johannes Fahrenkrug said...

Hi Eduard,

Thank you for your feedback. You find the image on github: http://github.com/jfahrenkrug/CappuccinoLocations1/tree/b00d7b9ca79b0de4a3e895a9adf966d76e6b18ca/Resources

- Johannes

September 09, 2009 03:04 PM

Eduard Bondarenko said...

Thanks for great and usefull tutorial.

One thing: I didn't have add.png, remove.png in Resources.

I didn't find that files in cappuccino resources also.

September 09, 2009 02:43 PM

Comments

Please keep it clean, everybody. Comments with profanity will be deleted.

blog comments powered by Disqus