Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Using the Web Kit for Application Registration

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