
In this article, you'll learn how to use the Web Kit to handle an application's
on-line registration. Registration has always been a bit tricky, so applications use various methods. Some
applications offer a button that spawns a web browser, but the registration site may use features not supported by the user's
default browser, and browser users get distracted and sometimes quit before they finish registering.
You could always create your own unique
form-registration process and write the code that displays, validates, and
submits registration information. But that's a labor-intensive undertaking;
unless you have a large-scale product, you may prefer a simpler, quicker way to
take registrations.
With the Web Kit, a set of classes for
displaying web content that is included in Xcode, you can easily integrate an
HTML-based registration form into your
application. Then, with simple web-based scripts, you can handle validation and
processing of user registration. While this may not work perfectly for all
situations, it provides a good combination of convenience and reliability.
The basic techniques discussed in this article can be applied to any
integration of the Web Kit into existing applications. In your own applications you
may find circumstances where you can use HTML rendering outside of the Web
browser.
This article has two parts: in the first part, you build the interface using
Xcode and Interface Builder. When you have your interface, you then can use the second part of this article to create the
backend, using code samples that show you how, and can also serve as a starting point. It assumes you are running Mac OS X 10.3 Panther, and using Xcode version 1.1 (or later).
Altering an Existing Application
Rather than build a Cocoa application from scratch, for this article we'll start
with one of the example projects in /Developer/Examples that ship
with Xcode. While it's unlikely that a user of the CircleView application would
want to register for updates, this example gives your version of the
application that functionality.
Start by making a copy of the CircleView application, located in the in the
AppKit subdirectory in /Developer/Examples/AppKit/CircleView. Rename
the new folder to "CircleReg" to keep the original. Now, make sure
everything is working, by opening the new project file in Xcode, and click on the
"Build and run active executable" button in Xcode's toolbar.
Part I: Interface Building
To build the interface, you need to create a couple of widgets. First, you'll need a window to hold
your web form. In the "Groups & Files" tab of the main Xcode window, open
the NIB Files group. Double-click on MainMenu.nib in the files pane—this
will open the interface in Interface Builder.
You will need a new class to attach your registration code to. On the
Classes tab of the Interface Builder window for MainMenu.nib, select NSObject>NSResponder>NSWindow. From the Classes menu, select "Subclass
NSWindow." Rename the new class from "MyWindow" to "RegWindow." This
will be the class that holds the code for the application registration
process. Select the RegWindow class, and select Show Info from the Tools
menu. Select Attributes from the pop-up menu at the top of the Info window,
and select the Outlets tab at the top of the pane in that window. Click
the Add button. A new Outlet is created, named myOutlet, of type
"id." Change the name to webView of type WebView, as shown in Figure 1: Adding a WebView Outlet.

Figure 1: Adding a WebView Outlet.
Select Create Files for RegWindow from the Classes menu. The default options
should be correct; you want to create both RegWindow.h and
RegWindow.m, and insert them into the target called CircleView;
see Figure 2: Inserting Files into the Target. Click Choose.

Figure 2: Inserting Files into the Target.
Now, create a new window to use this class. Go to the Cocoa-Windows tab
of the Interface Builder palette window. Drag the window icon out of the
palette, and a new window will appear. Select Show Info from the Tools
menu. Change the window's name to "Registration," and uncheck the checkboxes
for Miniaturize and Resize. Users shouldn't be able to alter this
window; it would just be confusing. See Figure 3: Defining the RegWindow.

Figure 3: Defining the RegWindow.
Select Custom Class from the pop-up
menu at the top of the info window, and select RegWindow from the list.
The window's default size (480x360) should be fine for this application,
but if your registration page was larger, you would need to change it, by
selecting Size from the pop-up menu.
Next, create a WebView object in this window. In the palette window is a tab
called Cocoa GraphicsViews, the seventh from the left—since Panther, this
palette includes a WebView object; see Figure 4: Selecting the WebView Custom View. Drag the WebView icon from the palette into
the Registration window. In the Info window, set its size and location using the
Size tab, then set its Custom Class to WebView using the Custom Class tab.

Figure 4: Selecting the WebView Custom View.
Finally, attach the registration window's webView outlet to the new WebView
object. To do this, go to the Instances tab in the MainMenu.nib window.
Control-click on the Window object which represents the registration window,
and drag over to the WebView object in the Registration window. This will
cause the Info window to open (if it's closed), and change to the Connections
selection from the pop-up menu. Select the webView outlet from the list, and
click Connect, as shown in Figure 5: Attaching the Outlet to the Object.

Figure 5: Attaching the Outlet to the Object.
There's your registration window. Now you'll need a way for the user to open
the window, namely, a menu. Move your cursor to the Apple menu from the menu
window. (It may actually be a blank spot next to the Edit menu—click
there and the menu should appear.) Drag an "Item" menu selection from the menu
tab of the palette into this menu, just below About CircleView. Open the Info
window and rename this menu item "Register..." as shown in Figure 6: Naming Register... Menu. By convention, since it will
open a new window in which you perform a task, it gets an ellipsis in its name.

Figure 6: Naming the Register... Menu.
Control-drag from the new menu item to the registration window title bar, and select
the makeKeyAndOrderFront target in the Info window, then click
Connect, as shown in Figure 7: Selecting the Target. Close the
registration window; you don't want it to be open when the application first
opens.

Figure 7: Selecting the Target.
Finally, add the WebKit Framework to your project. Select Add Frameworks...
from the Project menu. Navigate to the
System/Library/FrameworksFolder on your system disk, and select
WebKit.Framework.
Save your work. Test the interface, and make sure that the registration
window opens when you select the menu item. It's still empty now, but that's
fine.
Part II: Writing the Back End
This application uses a resource file called form.html, which contains
the actual registration form. Put the following HTML in a file named form.html in the CircleReg directory.
Listing 1: form.html
<html>
<head><title>Registration</title>
</head>
<body>
<form method="post" action="http://www.seebs.net/sample.cgi">
<table>
<tr><th>First name:</th><td><input type="text" name="first"></td></tr>
<tr><th>Last name:</th><td><input type="text" name="last"></td></tr>
<tr><th>Phone number:</th><td><input type="text" name="phone"></td></tr>
<tr><th> Mailbox:</th><td><input type="text" name="mail"></td></tr>
</table>
<p>Let us know who you are.</p>
<input type="submit" name="submit" value="Register">
</form>
</body>
In Xcode, select "Add Files..." from the project menu, and then select form.html.
Next, you need to alter the RegWindow.h that you created with Xcode.
Below is the code that belongs in the new RegWindow.h file:
Listing 2: RegWindow.h
/* RegWindow */
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
@interface RegWindow : NSWindow
{
IBOutlet WebView *webView;
}
- (void)reloadForm;
@end
This code adds a new instance method and a new instance variable. The instance
method reloadForm does the actual work of finding the local HTML
pages and loading them in the WebView object. The instance
variable holds a registration code.
The new code RegWindow.m, seen below, provides a number of instance
methods. Copy this over the RegWindow.m code that had been created by Xcode.
Listing 3: RegWindow.m
#import "RegWindow.h"
#import <Foundation/NSBundle.h>
#import <WebKit/WebUIDelegate.h>
#include <string.h>
@implementation RegWindow
- (void)displayErrorMessage:(NSString *)error {
NSBeginAlertSheet(
@"Error",
@"OK",
nil,
nil,
self,
self,
NULL,
NULL,
self,
error,
nil);
}
- (void) webView:(WebView *)sender decidePolicyForMIMEType:(NSString *) type request:(NSURLRequest *)
request frame:(WebFrame *)frame decisionListener:(id)listener {
[listener use];
}
- (void)webView:(WebView *)webView decidePolicyForNewWindowAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
newFrameName:(NSString *)frameName
decisionListener:(id)listener {
[listener ignore];
}
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
frame:(WebFrame *)frame
decisionListener:(id)listener {
NSURL *url = [request URL];
NSString *urlString = [url absoluteString];
if ([urlString hasSuffix:@"close=true"]) {
const char *s = [urlString cString];
const char *reg = strstr(s, "reg=");
if (reg) {
const char *end;
reg = reg + 4;
end = strchr(reg, '+');
printf("registration code %.*s\n", end ? (end - reg) : strlen(reg), reg);
}
[listener ignore];
[self close];
[self reloadForm];
return;
}
printf("navigate: %s\n", [[url absoluteString] cString]);
[listener use];
}
- (void)reloadForm {
NSBundle *bundle = [NSBundle mainBundle];
if (bundle == nil) {
[self displayErrorMessage: @"No mainBundle"];
return;
}
NSString *path = [bundle bundlePath];
if (path == nil) {
[self displayErrorMessage: @"Application bundle has no path?"];
return;
}
NSString *fullPath = [NSBundle pathForResource:@"form" ofType:@"html" inDirectory:path];
if (fullPath == nil) {
[self displayErrorMessage: @"Can't find form.html."];
return;
}
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:fullPath]]];
}
- (void)awakeFromNib{
[self reloadForm];
[webView setPolicyDelegate:self];
}
@end
This code is explained in detail in the next section.
Since these files refer to the Web Kit, you need to add the Web Kit framework to your project. Select Add Frameworks...
from the Project menu. Navigate to the System/Library/Frameworks folder
on your system disk, and select WebKit.framework.
Now expand the CircleView group. The file form.html will be a top-level part of the
CircleView project. Drag it into the Resources folder. Build and run the
application. You should now see a registration form if you select the
Register... menu item.
Once you've got these plugged in, try the application. If you have an appropriate CGI
script for it to submit to, it will let you register your application (a
sample CGI script is provided below along with an explanation of its use).
What's Going On?
When the "Register..." menu item is selected, the RegWindow class's
makeKeyAndOrderFront method is called. This is the routine which
actually draws the window. It does several things:
- First, it calls
super makeKeyAndOrderFront. This causes
the superclass (NSWindow) to do all the hard work.
- Second, it checks the NSUserDefaults database for an
existing registration code. This corresponds to an entry in
com.apple.examples.CircleView.plist in
~/Library/Preferences. If you want to re-register, you have to
delete that file.
- Finally, it calls the
reloadForm method, which does the
actual work of loading an HTML page.
The reloadForm method finds the form and passes it on for the Web Kit
to render. The elaborate series of NSBundle calls makes the
application work no matter where it's installed; as long as the
pages were installed as part of the application, they will be found.
If a registration code has already been found, then the application
generates an HTML string in a buffer and loads it, telling the
user what the registration code is. If there's no registration
code, the form to get one is loaded.
The form submission is pretty straightforward. What's interesting
is the way the registration code is extracted. It would be annoying to
try to read the actual web page returned, so the program doesn't do
that. Instead, it sets a policy delegate up for the WebView object.
A policy delegate is any object which implements the
WebPolicyDecisionListener protocol. That's really just three functions to implement, called decidePolicyForNewWindowAction,
decidePolicyForNavigationAction, and decidePolicyForMIMEType. For
this trivial browser, which is designed to only process one form, two
of these have limited use. NewWindowAction always sends an ignore
message; this program never wants to open a new window. MIMEType always sends a use message; anything sent back by the server is presumed okay.
The bulk of the work happens in the NavigationAction policy
listener. This method is called with, among other things, a URL that the policy
delegate is supposed to make a decision on. The sample CGI script produces an HTTP meta
refresh tag. That tag redirects the user agent to a URL that ends with
"reg=code". The NavigationAction policy delegate then looks for
the 'reg=code' string within the URL. If it's there, the delegate scans the URL
for the returned registration code, and sends an ignore message to the listener
(telling the Web Kit code not to follow that URL). It also saves the
registration code using the NSUserDefaults class. The window can be
closed manually by the user. Alternatively, a link on the form goes URL ending
in 'close=true'. The policy delegate also looks for a URL like that, and if it
sees one, tells the Web Kit not to follow the link, and closes
the window.
If you reopen the window, what happens? The makeKeyAndOrderFront
method synchronizes with the preference database, finding the registration code.
It then displays a page saying the user is already registered.
The policy delegate is set in awakeFromNib, rather than in
makeKeyAndOrderFront. This is important! If it were not set up
this way, there might be a race condition where the window was browsing before
the policy delegate was in place.
Summary
This article offers just a simple example to get you started; you can create
custom code for your application that does much more. The
WebPolicyDecisionListener protocol is extremely flexible, and lets
you do a wide range of maintenance tasks. For instance, if you have a CGI
script which, given a URL, performs some trivial alteration on that URL, you
could build a web browser which runs every URL it sees through that script. You
can log URLs. You can check for patterns you don't like. You can block sites,
you can block types of files. You could resolve host names in URLs, and block
names pointing to certain IP numbers.
As you saw in this application, you can even make use of data
returned by the server. As long as you control both ends, you can do
pretty much anything. If you want to change your registration form, no problem;
have the CGI script take the data provided, and funnel it into a new form,
which explains to the user why you need more data than you previously asked
for. All the program knows is that, if it sees a
URL with 'reg=string' in it, that it's found your registration code.
Updated: 2008-01-02
|