In the previous four chapters, we covered the front end of Cocoa applications, windows and views, and how you can use controllers behind them. Now we turn our attention to the back end—the model—and how the data functionality of an application works.
To take full advantage of Cocoa’s data-handling mechanisms, we must first explain two concepts that we didn’t cover when we first introduced Objective-C and the Foundation Kit (see Chapter 4): protocols and key-value coding. After covering these topics, we deal with how to connect a user interface to an underlying data model and how that model can be saved and opened.
Many pieces of Cocoa functionality make use of an Objective-C language feature called a protocol. A protocol is simply a list of method declarations. A class is said to conform to a protocol when it provides implementations of the methods that a protocol defines.
To help explain the concept of a protocol, think of the similarities between a waiter at a restaurant and a vending machine.[14] Even though the waiter and the vending machine are nowhere close to being similar objects from an inheritance standpoint, they can both implement methods related to serving food and taking money. Roughly, we could describe a protocol implemented by these two objects as the following methods:
takeOrder serveFood takeMoney returnChange complainTo
Of course, a vending machine doesn’t usually serve very tasty or nutritious food and doesn’t respond very well, if at all, to complaints. Additionally, you usually have to give vending machines money before they will take your order. But let’s not get caught up too much in the details of our analogy. At a very basic level, the vending machine and waiter aren’t all that different from each other—at least from the point of view of the person getting food. And note that it is easy to take this protocol and find other food-service situations in which it applies, such as getting a donut from the local convenience store.
In object-oriented programming, protocols are useful in the following situations:
To declare methods that other classes are expected to implement. This lets programs define methods that they will call on objects but that other developers will actually implement, and this is crucial to loading bundles and plug-ins.
To declare the public interface to an object while concealing its class. This lets more than one implementation of an object “hide” behind a protocol and prevents users from using unadvertised methods.
To capture similarities among classes that are not hierarchically related. Classes that are unrelated in most respects might need to implement similar methods for use by similar outside components. Protocols help formalize these relationships while preserving encapsulation.
Objective-C defines two kinds of protocols: informal and formal. An informal protocol uses categories to list a group of methods but doesn’t associate them with any particular class or implementation.
A formal protocol , on the other hand, binds the list methods to a type definition that allows typing of objects by protocol name. Additionally, when a class declares that it implements a formal protocol, all of the methods of the protocol must be implemented.
Key-value coding
is a kind of shorthand. It is defined
by an informal protocol used for accessing instance variables (also
known as properties) indirectly by using string
names (known as
keys
),
rather than directly through the invocation of an accessor
method[15] or as
instance variables. The key-value coding informal protocol (more
accurately, the NSKeyValueCoding
protocol) is
available for use by any object that inherits from
NSObject
. Several Cocoa components, as well as its
scripting support, take advantage of key-value coding.
The two basic methods for manipulating objects using the key-value coding protocol are as follows:
valueForKey:
Returns the value for the property
identified by the key. The default implementation searches for a
public accessor method based on the key name given. For example, if
the key name given is price, a method named
price
or getPrice
will be
invoked. If neither method is found, the implementation will look for
an instance variable named price
and access it
directly.
takeValue:forKey:
Sets the value for
the property identified by the key to the value given. For our
example of price, the default implementation
will search for a public accessor method named
setPrice
. If the method is not found, it will
attempt to access the price
instance variable
directly.
To
show key-value coding in action, we will create a simple example
based on a FoodItem
class. By now, you should be
familiar with what you’ll see in this example,
except for how we use key-value coding. Perform the following steps:
Create a new Foundation Tool in Project Builder (File → New Project → Tool → Foundation Tool) named “keyvaluecoding”, and save it in your ~/LearningCocoa folder.
Create a new Objective-C class named FoodItem
(File → New File → Cocoa
→ Objective-C Class), as shown in Figure 9-1. Be sure to create both the
.h and .m files.
Edit the FoodItem.h file as follows:
#import <Foundation/Foundation.h> @interface FoodItem : NSObject { NSString * name; // a NSNumber * price; // b } - (NSString *)name; // c - (void)setName:(NSString *)aName; // d - (NSNumber *)price; // e - (void)setPrice:(NSNumber *)aPrice; // f @end
This code adds the following things:
The name
instance variable of type
NSString
. This variable will store the name of the
food item.
The price
instance variable of type
NSNumber
. This variable will store the price of
the food item.
Accessor method that returns the name
of the food
item.
Accessor method that allows the name of the food item to be set.
Accessor method that returns the price
of the food
item.
Accessor method that allows the price of the foot item to be set.
Edit the FoodItem.m file as follows:
#import "FoodItem.h" @implementation FoodItem - (id)init // a { [super init]; [self setName:@"New Item"]; [self setPrice:[NSNumber numberWithFloat:0.0]]; return self; } - (NSString *)name // b { return name; } - (void)setName:(NSString *)newName // c { [newName retain]; [name release] name = newName; } - (NSNumber *)price // d { return price; } - (void)setPrice:(NSNumber *)newPrice // e { [newPrice retain]; [price release]; price = newPrice; } @end
The code we added performs the following tasks:
Initializes the object with some default values.
Implements the name
accessor method.
Implements the setName:
accessor method. Notice
that we retain the new object, release the old one, then set the
name
variable to the new object, in accordance
with the rules we discussed in Chapter 4.
Implements the price
accessor method.
Implements the setPrice:
accessor method.
Edit the main.m file (located in the Sources folder in Project Builder’s left pane) as follows:
#import <Foundation/Foundation.h> #import "FoodItem.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; FoodItem * candyBar = [[FoodItem alloc] init]; [candyBar takeValue:@"Aero" forKey:@"name"]; // a [candyBar takeValue:[NSNumber numberWithFloat:1.25] forKey:@"price"]; // b NSLog(@"item name: %@", [candyBar valueForKey:@"name"]); // c NSLog(@" price: %@", [candyBar valueForKey:@"price"]); // d [candyBar release]; // e [pool release]; return 0; }
The code that we added performs the following tasks:
Instructs the candyBar
object to set the
name
instance variable to
Aero
.[16]
Instructs the candyBar
object to set the
price
instance variable to
1.25
. We use the NSNumber
class
to wrap primitive types for use as objects in collections and in
key-value coding.
Instructs the candyBar
object to return the object
assigned to the name
variable and prints it using
the NSLog
function.
Instructs the candyBar
object to return the object
assigned to the price
variable and prints it out
using the NSLog
function.
Releases the candyBar
object.
Save the project (
-S), and then build and run the application (Build → Build and Run, or
-R).
The following output appears in Project Builder’s console:
2002-04-10 15:31:57.584 Key Value Coding[1382] item name: Aero 2002-04-10 15:31:57.585 Key Value Coding[1382] price: 1.25 Key Value Coding has exited with status 0.
Obviously, for a real program of this length, using key-value coding is overkill compared to setting and retrieving the instance variables directly through accessor methods. Where key-value coding comes into its own is for hooking up model objects—those that implement logic and/or store data—to the generic view objects that Cocoa provides.
Some of Cocoa’s view components let you define an identifier attribute. When the identifier attribute is set to a property key name for a model object, the component can automatically get, display, and set the value of the property without having to know anything about its implementation. We’re going to see how this works with table views in the next section.
We’re now done with this example application. Close the project in Project Builder before moving on.
Table views are objects that display data as rows and columns. In a table view, a row typically maps to one object in your data model, while a column maps to an attribute of the object for that row. Figure 9-2 shows a table view and its component parts.
In Model-Viewer-Controller (MVC) terms, a data source is a controller object that communicates with a model object (typically an array) and the view object. This relationship is shown in Figure 9-3.
To have their data displayed properly, model objects must implement a
couple of methods from the NSTableDataSource
informal protocol:
- (int)numberOfRowsInTableView:(NSTableView *)tableView; - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row;
The first method allows the table view to ask its data source how many rows of data are in the data model. The second method is used by the table view to retrieve an object value from the data model by row and column.
To show how table views and models go together, we’ll build a simple application to keep track of food items in a form that might be used to generate a menu. In Project Builder, create a new Cocoa Application project (File → New Project → Application → Cocoa Application) named “Menu”, and save it in your ~/LearningCocoa folder. Then open the MainMenu.nib file (located in the Resources folder of Project Builder’s left pane) in Interface Builder.
To create the interface, perform the following steps:
Title the main window “Menu” in the Window Title field of the Info window (Tools → Show Info, or Shift-
-I).
Drag a table view object (NSTableView
) from the
Cocoa-Data views palette, as shown in Figure 9-4.
Resize the table view to fill the window.
Change the Autosizing attributes so that the table view will always occupy the entire window, as shown in Figure 9-5.
The next step is to configure the columns by adjusting their width, giving them titles, and, most importantly, assigning identifiers to the columns so Cocoa’s key-value coding can operate.
Make the width of the columns equal. Select the leftmost column (you may have to double-click), hold the cursor over the right edge of the column so that it turns into a pair of horizontally opposed arrows, then drag the column edge so the column view is divided in half.
Double-click on the header bar for the left column and type Item Name, then press Tab to move to the header bar for the right column.
Type Price as the header for the right column, then press Tab to select the left column.
Edit the Identifier field for the left column in the Attributes pane of the Info panel so it reads name, as shown in Figure 9-6.
Don’t confuse the Column Title field, located at the top of the Attributes panel, with the Identifier field at the bottom of the window. These serve two entirely different purposes. The Column Title field is for the benefit of your users and should contain the title you’ve assigned to that column in steps 2 and 3. The Identifier is an internal programmatic name that refers to the name of the property that should be displayed in the column.
Repeat for the right column, assigning it an Identifier of price.
A data source can be any object in your application that supplies the table view with data.
Create a subclass of NSObject
, and name it
MyDataSource
.
Instantiate the MyDataSource
class (Classes
→ Instantiate MyDataSource).
Draw a connection from the table view object to the
MyDataSource
object in the Instances window. Make
sure that you have selected the table view, not its surrounding
scroll view before you draw the connection. The table view will turn
a darker shade of gray when selected.
Select the dataSource
outlet in the Connections
pane of the Info window, as shown in Figure 9-7,
and click the Connect button.
Click on MyDataSource
in the Classes tab of the
MainMenu.nib window, and create the interface
files (Classes → Create Files for MyDataSource).
Save (
-S) the nib, and return to Project Builder.
The back end of our
table will consist of two classes: the
MyDataSource
class that we defined in Interface
Builder and the same FoodItem
class that we
created earlier in this chapter.
Add the files for the FoodItem
class to the
project. Using the Finder, locate the FoodItem.h
and FoodItem.m files (in the
~/LearningCocoa/Key View Coding directory), and
drag them into the Other Sources folder of the Groups & Files
panel in Project Builder. When the sheet appears to confirm your
copy, ensure that the “Copy items”
checkbox is selected, and then click the Add button, as shown in
Figure 9-8.
Open the MyDataSource.h file, and edit it to match the following code:
#import <Cocoa/Cocoa.h>
@interface MyDataSource : NSObject
{
NSMutableArray * items;
}
@end
The code we added in the header file simply declares a single array,
named items
, as an instance variable. We will hold
the many items to be displayed in the user interface of our
application in this array.
Open the MyDataSource.m file, and edit it to match the following code:
#import "MyDataSource.h" #import "FoodItem.h" @implementation MyDataSource - (id)init { [super init]; // Some initial data for our interface FoodItem * chimi = [[FoodItem alloc] init]; // a FoodItem * fajitas = [[FoodItem alloc] init]; [chimi setName:@"Chimichanga"]; [chimi setPrice:[NSNumber numberWithFloat:8.95]]; [fajitas setName:@"Steak Fajitas"]; [fajitas setPrice:[NSNumber numberWithFloat:10.95]]; items = [[NSMutableArray alloc] init]; [items addObject:chimi]; [items addObject:fajitas]; [chimi release]; [fajitas release]; // b return self; } - (int)numberOfRowsInTableView:(NSTableView *)tableView { return [items count]; // c } - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row { NSString * identifier = [tableColumn identifier]; // d FoodItem * item = [items objectAtIndex:row]; // e return [item valueForKey:identifier]; // f } @end
The code that we added performs the following tasks:
Creates a couple of sample menu items and puts them into an
NSMutableArray
instance.
Releases the food items, now that they are stored safely in the array.
Returns the number of items
in the food items
array. This lets the table view know how many rows contain data.
Gets the identifier
of the column for which the
table view wants data.
Obtains the food item
that is at the specified
index in the array.
Returns the value of the food item object that matches the property
name of the identifier
obtained from the table
column.
Save the project (File → Save, or
-S), and then build and run the application (Build → Build and Run, or
-R). You should see something like Figure 9-9.
Play with the application a little bit: resize the window; resize the individual table columns; reorder the table columns by dragging around the column headers. Quit the application (
-Q) when you are done.
When playing with our Menu application so far, you could select the items in the table, and you could even edit them. But when you try to complete editing an item name or a price by hitting Return or exiting the table cell, the edit doesn’t take. To let the user edit the fields, a third method must be implemented to save changes back to the data source:
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row;
Add the following method to our MyDataSource.m
file after the
tableView:objectValueForTableColumn:row:
method:
- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row { NSString * identifer = [tableColumn identifier]; // 1 FoodItem * item = [items objectAtIndex:row]; // 2 [item takeValue:object forKey:identifer]; // 3 }
The code that we added does the following things:
Gets the identifier
of the column for which the
table view wants to set data.
Obtains the food item
that is at the specified
index in the array.
Sets the property of the food item that matches the
identifier
that we obtained in step 1.
Now save the project (File → Save, or
-S). Before you can build and run the application to test the editing features, you first need to ensure that you have quit out of any running Menu application. Build and run the app again (Build → Build and Run, or
-R) from within Project Builder. You should now be able to edit the fields in the table and have those changes made in the underlying data model.
With an application like Menu, adding entries to the model can be useful. The following steps guide you through the process of adding this functionality to the application:
We’re going to add a button to the interface. To
enable a new row to be added when this button is pushed,
we’ll need to add an action
newButtonPressed:
and an outlet
table
to MyDataSource
. An easy
way to do this is to add the declarations yourself in the code. In
Project Builder, edit the MyDataSource.h file to
match the following code. The code you need to add is shown in
boldface.
#import <Cocoa/Cocoa.h> @interface MyDataSource : NSObject { NSMutableArray * items; IBOutlet NSTableView * table; } - (IBAction)newButtonPressed:(id)sender; @end
Save the header file (File → Save, or
-S).
In Interface Builder’s Classes pane, select
MyDataSource
, and reload the source file (Classes
→ Read MyDataSource.h). This causes Interface
Builder to reparse the header file and pick up the new outlet and
action.
Resize the table view to make room for a button.
Drag a button from the Cocoa-Views panel onto the interface, and change its name to New Item.
Select the table view, and reset its Autosizing attributes as shown in Figure 9-10.
Control-drag a connection between the MyDataSource
object in the Instances tab of the MainMenu.nib
window and the table view. Connect it to the table
outlet, as shown in Figure 9-11.
Control-drag a connection between the New Item button and the
MyDataSource
object in the Instances tab. Connect
it to the newActionPressed:
button, as shown in
Figure 9-12.
Save the nib file (File → Save, or
-S), and return to Project Builder.
Edit the MyDataSource.m file, adding the
newButtonPressed:
method shown in the following
code:
- (IBAction)newButtonPressed:(NSEvent *)event { FoodItem * item = [[FoodItem alloc] init]; // a [items insertObject:item atIndex:0]; // b [item release]; [table reloadData]; // c [table selectRow:0 byExtendingSelection:NO]; // d }
The code we added performs the following tasks:
Creates a new item
object.
Inserts the new item
object into our data model
array.
Instructs the table view to reload its data. This will cause the
table view to call the numberOfRowsInTableView:
method again and load all the rows from the model.
Selects the row we just added into the table. This highlights the new row, so the user of the application can edit it.
Save the project files (File → Save, or
-S), and then build and run the application (Build → Build and Run, or
-R). When you press the New Item button, a new row should be created, as shown in Figure 9-13.
To edit the new fields, simply click in either column, and enter a new food item and price.
Virtually all applications need to make some of their objects persistent. In user-speak, this means that all applications need a way to save their data. For example, the Menu application doesn’t save the state of the data model, so all changes are lost as soon as you quit the application. Cocoa applications typically use coding and archiving to store document contents and other critical application data to disk for later retrieval. Some applications may also use coding and archiving to send objects over a network to another application.
Coding, as implemented by the
NSCoder
class, takes a connected group of
objects, such as the array of food items in our sample application
(an object graph), and serializes that data.
During
serialization,
the state, structure, relationships, and class memberships are
captured. To be serialized, an object must conform to the
NSCoding
protocol (consisting of the
encodeWithCoder:
and
initWithCoder:
methods).
Archiving, as implemented by the
NSArchiver
class (which extends
NSCoder
), extends this behavior by storing the
serialized data in a file.
To show how to archive objects, we will
modify our Menu application to save and load files that contain the
list of food items. To do this, we need to hook up the File
→ Open and File → Save menu items,
add the save and open sheet functionality, and make sure that the
FoodItem
class can be archived.
In Project Builder, open FoodItem.h and modify
the @interface
declaration as follows. Adding
<NSCoding>
declares that the
Song
class conforms to the coding protocol.
#import <Foundation/Foundation.h>
@interface FoodItem : NSObject <NSCoding> {
NSString * name;
NSNumber * price;
}
.
.
.
NSCoding
appears in brackets to signify that the
FoodItem
interface implements the coding protocol.
If you were to read this declaration aloud, it would sound like:
"FoodItem
extends from the
NSObject
class and implements the
NSCoding
protocol.”
Open the FoodItem.m file, and add the
NSCoding
methods after the init
method as follows:
- (id)initWithCoder:(NSCoder *)coder { [super init]; [self setName:[coder decodeObject]]; // a [self setPrice:[coder decodeObject]]; // b return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:[self name]]; // c [coder encodeObject:[self price]]; // d }
The code we added performs the following tasks:
Decodes the next object from the
coder
’s data stream and sets the
name
instance variable.
Decodes the next object from the
coder
’s data stream and sets the
price
instance variable.
Encodes the name
instance variable to the
coder
’s data stream.
Encodes the price
instance variable to the
coder
’s data stream.
Open MyDataSource.h, and add the following two action methods:
#import <Cocoa/Cocoa.h> @interface MyDataSource : NSObject { NSMutableArray * items; IBOutlet NSTableView * table; } - (IBAction)newButtonPressed:(id)sender; - (IBAction)save:(id)sender; - (IBAction)open:(id)sender; @end
Save the source files, and then open the MainMenu.nib file in Interface Builder.
Reparse the MyDataSource.h file in Interface
Builder. To do this, click on the MyDataSource
object in the Classes tab, and then select the Classes
→ Read File MyDataSource.h menu option.
Click on the File menu of the MainMenu.nib - MainMenu window to reveal the menu options.
Control-drag a connection from the File → Open...
menu item to the MyDataSource
instance in the
Instances tab, as shown in Figure 9-14. Connect it
to the open:
target.
Control-drag a connection from the File → Save menu
item to the MyDataSource
instance. Connect it to
the save:
target.
Save the nib file (File → Save, or
-S), and return to Project Builder.
Add the save:
and open:
methods
to MyDataSource.m, as well as two helper
methods, as shown here:
#import "MyDataSource.h" #import "FoodItem.h" @implementation MyDataSource . . . - (IBAction)save:(id)sender { NSSavePanel * savePanel = [NSSavePanel savePanel]; // a SEL sel = @selector(savePanelDidEnd:returnCode:contextInfo:); // b [savePanel beginSheetForDirectory:@"~/Documents" // c file:@"menu.items" modalForWindow:[table window] modalDelegate:self didEndSelector:sel contextInfo:nil]; } - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)context { if (returnCode == NSOKButton) { // d [NSArchiver archiveRootObject:items toFile:[sheet filename]]; } } - (IBAction)open:(id)sender { NSOpenPanel * openPanel = [NSOpenPanel openPanel]; // e SEL sel = @selector(openPanelDidEnd:returnCode:contextInfo:); [openPanel beginSheetForDirectory:@"~/Documents" file:nil types:nil modalForWindow:[table window] modalDelegate:self didEndSelector:sel contextInfo:nil]; } - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { if (returnCode == NSOKButton) { NSMutableArray * array; // f array = [NSUnarchiver unarchiveObjectWithFile:[sheet filename]]; [array retain]; [items release]; items = array; [table reloadData]; } } @end
The code that we added does the following things:
Creates a new Save panel—Cocoa’s standard user-interface widget for selecting where a file should be saved. The way we use the Save panel uses delegation in a manner similar to the sheet we added to the Dot View application (Chapter 8).
Obtains the selector
for the callback method that
the Save panel should use when the user has selected the file to
which data will be saved.
Instructs the Save panel to display itself as a sheet attached to the
current window. MyDataSource
doesn’t have a direct reference to the window to
which the sheet should be attached, but since it does have a
reference to the table
, we can simply ask the
table
for the window
object.
Archives the items
array to the given file if the
callback method gets a status code indicating that the user selected
the file to which to save.
Creates a new Open panel—Cocoa’s standard user-interface widget for selecting files to open. Open panels work very much like save panels.
Unarchives an array
object from the file selected
by the user; this releases the old array assigned to the
items
variable and assigns a retained instance of
the new items
.
Now save the project (File → Save, or
-S), and then build and run the application (Build → Build and Run, or
-R).
Add a few items to your list of food items, then save (
-S), and you should see the save dialog sheet slide out from the titlebar of the application window, as shown in Figure 9-15.
Quit the application, restart it (
-R), and then open (
-O) the data file you just saved. All the changes you made should show up. Make sure to quit (
The next task for the Menu application is to add a formatter to the Price column so that the amounts of our food items are shown using a currency format.
Open MainMenu.nib in Interface Builder.
Drag a currency formatter (NSNumberFormatter
) from
the Cocoa-Views palette to the price column, as shown in Figure 9-16.
In the number-formatter inspector, set up the format to use the currency settings shown in Figure 9-17.
Save the nib file.
Return to Project Builder, and build and run the application (Build → Build and Run, or
-R). The menu interface should look like Figure 9-18.
The last thing we will add to our Menu
application is the ability for the contents of the table to be sorted
when a table column header is clicked. To do this, we will rely upon
the ability of Cocoa collections to be sorted using comparators. We
will add comparison methods to the FoodItem
class
so that an instance object can say that it should be sorted either
before or after another instance.
In Interface Builder, set the data view as the delegate of the table
view by Control-dragging a connection between the table view and the
MyDataSource
instance object and connecting it to
the delegate outlet, as shown in Figure 9-19.
Save the nib file (File → Save or
-S), and return to Project Builder.
Open the FoodItem.m file, and add the following two methods to the file after the other methods:
- (NSComparisonResult)compareName:(FoodItem *) item // a { return [name compare:[item name]]; } - (NSComparisonResult)comparePrice:(FoodItem *) item // b { return [price compare:[item price]]; }
These methods perform the following actions:
This method returns a comparison result by using the
compare:
method of the NSString
class to compare the name
of the given object with
the name
of the current instance.
This method returns a comparison result by using the
compare:
method of the NSNumber
class to compare the price
of the given object
with the price
of the current instance.
Open the MyDataSource.m file, and add the following method:
- (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn { NSString * identifier = [tableColumn identifier]; // a if ([identifier isEqualToString:@"name"]) { [items sortUsingSelector:@selector(compareName:)]; // b } else { [items sortUsingSelector:@selector(comparePrice:)]; // c } [table reloadData]; // d }
This method does the following things:
Obtains the identifier
from the column so that we
know with which property to sort.
Tells the items
array to sort itself using the
compareName:
method of each item in the array.
Tells the items
array to sort itself using the
comparePrice:
method of each item in the array.
Tells the table
view that the underlying data has
changed and that it needs to reload itself.
Build and run the application (Build → Build and Run, or
-R). Add a few items to the Menu, and then sort by name, then price, and see the results.