Apple Apple Computer WebObjects Dev Guide

WEBOBJECTS DEVELOPER’S GUIDE
Apple, NeXT, and the publishers have tried to make the information contained in this manual as accurate and reliable as possible, but assume no responsibility for errors or omissions. They disclaim any warranty of any kind, whether express or implied, as to any matter whatsoever relating to this manual, including without limitation the merchantability or fitness for any particular purpose. In no event shall they be liable for any indirect, special, incidental, or consequential damages arising out of purchase or use of this manual or the information contained herein. NeXT or Apple will from time to time revise the software described in this manual and reserves the right to make such changes without obligation to notify the purchaser.
Copyright 1997 by Apple Computer, Inc., 1 Infinite Loop, Cupertino, CA 95014. All rights reserved. [7010.01]
No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the publisher or copyright owner. Printed in the United States of America. Published simultaneously in Canada.
NeXT, the NeXT logo, OPENSTEP, Enterprise Objects, EOF, Enterprise Objects Framework, ProjectBuilder, Objective-C, Portable Distributed Objects, Workspace Manager, Database Wizard, WEBSCRIPT, and WEBOBJECTS are trademarks of NeXT Software, Inc. PostScript is a registered trademark of Adobe Systems, Incorporated. Windows NT is a trademark of Microsoft Corporation. UNIX is a registered trademark in the United States and other countries, licensed exclusively through X/Open Company Limited. ORACLE is a registered trademark of Oracle Corporation, Inc. SYBASE is a registered trademark of Sybase, Inc. All other trademarks mentioned belong to their respective owners.
Restricted Rights Legend: Use, duplication, or disclosure by the Government is subject to restrictions as set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer Software clause at DFARS 252.227-7013 [or, if applicable, similar clauses at FAR 52.227-19 or NASA FAR Supp. 52.227-86].
This manual describes WebObjects, version 3.5.
Written by Terry Donoghue, Katie McCormick, Matt Morse, Jean Ostrem, Kelly Toshach With help from Eric Bailey, Craig Federighi, Patrice Gautier, Francois Jouaux, Charles Lloyd, and Dan Peknik Developmental Editing by Jeanne Woodward Proofread by Laurel Rezeau Book Design by Karin Stroud Technical illustrations by Karin Stroud Print and Online Production Editing by Gerri Gray Art, Production, and Editorial management by Gary Miller Technical publications management by Greg Wilson
Contents
Table of Contents
Introduction 9
About This Book 11
Other Useful Documentation 12
Part I WebObjects Essentials
What Is a WebObjects Application? 17
The Ingredients of a WebObjects Application 19
Components 20
Template 21
Code or Script File 21
Bindings 22
Application Code 23
Session Code 23
A Note on WebObjects Classes 23
Application Directory 24
Running a WebObjects Application 25
WebObjects Adaptors 26
The WebObjects Application Executable 27
Dynamic Elements 29
Server-Side Dynamic Elements 31
How Server-Side Dynamic Elements Work 33
Binding Values to Dynamic Elements 35
Declarations File Syntax 36
Client-Side Java Components 37
Deciding When to Use Client-Side Components 37
How Client-Side Components Work 38
Common Methods 41
Action Methods 43
Initialization and Deallocation Methods 46
The Structures of init and awake 46
Application Initialization 47
Session Initialization 48
Component Initialization 49
Request-Handling Methods 50
Taking Input Values From a Request 51
Invoking an Action 52
Limitations on Direct Requests 53
Generating a Response 53
Debugging a WebObjects Application 55
Launching an Application for Debugging 57
Debugging WebScript 57
Debugging Java 57
Debugging Objective-C 58
Debugging Mixed Applications 58
Debugging Techniques 58
Writing Debug Messages 58
Using Trace Methods 59
Isolating Portions of a Page 60
Programming Pitfalls to Avoid 60
WebScript Programming Pitfalls 60
Java Programming Pitfalls 61
WebObjects Viewed Through Its Classes 63
The Classes in the Request-Response Loop 65
Server and Application Level 65
Session Level 66
Request Level 68
Page Level 69
Database Integration Level 71
v
How WebObjects Works—A Class Perspective 72
Starting the Request-Response Loop 72
Taking Values From the Request 73
Accessing the Session 74
Creating or Restoring the Request Page 76
Assigning Input Values 78
Invoking an Action 79
Generating the Response 80
How HTML Pages Are Generated 82
Component Templates 82
Associations and the Current Component 84
Associations and Client-Side Java Components 85
Subcomponents and Component References 86
Part II Special Tasks
State in the Page 124
State in Cookies 126
Custom State-Storage Options 128
Storing State for Custom Objects 131
Archiving Custom Objects in a Database Application 131
Archiving Custom Objects in Other Applications 132
Controlling Session State 133
Setting Session Time-Out 134
Using awake and sleep 135
Controlling Component State 135
Managing Component Resources 135
Adjusting the Page Cache Size 136
Using awake and sleep 137
pageWithName: and Page Caching 138
Client-Side Page Caching 139
Page Refresh and WODisplayGroup 140
Creating Reusable Components 91
Benefits of Reusable Components 93
Centralizing Application Resources 93
Simplifying Interfaces 96
Intercomponent Communication 98
Synchronizing Attributes in Parent and Child Components 102
Sharing Reusable Components Across Applications 104
Search Path for Reusable Components 105
Designing for Reusability 106
Managing State 109
Why Do You Need to Store State? 111
When Do You Need to Store State? 112
Objects and State 113
The Application Object and Application State 113
The Session Object and Session State 115
Component Objects and Component State 118
State Storage Strategies 120
Comparison of Storage Options 121
A Closer Look at Storage Strategies 122
State in the Server 123
Creating Client-Side Components 141
Choosing a Strategy 143
When You Have an Applet’s Source Code 144
When You Don’t Have an Applet’s Source Code 146
Deployment and Performance Issues 149
Recording Application Statistics 151
Maintaining a Log File 151
Accessing Statistics 152
Recording Extra Information 153
Error Handling 154
Automatically Terminating an Application 155
Performance Tips 156
Cache Component Definitions 156
Compile the Application 157
Control Memory Leaks 157
Limit State Storage 158
Limit Database Fetches 158
Limit Page Sizes 158
Installing Applications 159
vi
Part III WebScript
The WebScript Language 163
Objects in WebScript 165
WebScript Language Elements 166
Variables 166
Variables and Scope 167
Assigning Values to Variables 168
Methods 170
Invoking Methods 171
Accessor Methods 171
Sending a Message to a Class 172
Creating Instances of Classes 173
Data Types 174
Statements and Operators 175
Control-Flow Statements 176
Arithmetic Operators 176
Logical Operators 176
Relational Operators 176
Increment and Decrement Operators 177
Reserved Words 178
“Modern” WebScript Syntax 179
Advanced WebScript 181
Scripted Classes 181
Categories 182
WebScript for Objective-C Developers 183
Accessing WebScript Methods From Objective-C Code 185
WebScript Programmer’s Quick Reference to
Foundation Classes 187
Foundation Objects 189
Representing Objects as Strings 189
Mutable and Immutable Objects 189
Determining Equality 190
Writing to and Reading From Files 190
Writing to Files 190
Reading From Files 191
Working With Strings 191
Commonly Used String Methods 192
Creating Strings 192
Combining and Dividing Strings 193
Comparing Strings 194
Converting String Contents 194
Modifying Strings 195
Storing Strings 195
Working With Arrays 196
Commonly Used Array Methods 196
Creating Arrays 197
Querying Arrays 197
Sorting Arrays 198
Adding and Removing Objects 198
Storing Arrays 200
Representing Arrays as Strings 200
Working With Dictionaries 200
Commonly Used Dictionary Methods 201
Creating Dictionaries 202
Querying Dictionaries 203
Adding, Removing, and Modifying Entries 204
Representing Dictionaries as Strings 205
Storing Dictionaries 205
Working With Dates and Times 206
The Calendar Format 206
Date Conversion Specifiers 206
Commonly Used Date Methods 207
Creating Dates 207
Adjusting a Date 207
Representing Dates as Strings 208
Retrieving Date Elements 208
Index 211
vii
Introduction
About This Book
WebObjects is an object-oriented environment for developing and deploying World Wide Web applications. A WebObjects application runs on a server machine, receives requests from a user’s web browser on a client machine, dynamically generates the appropriate HTML page in response to those requests, and returns that page to the user. WebObjects provides you with a web application server, prebuilt application components, and a suite of tools for rapid development of World Wide Web applications.
WebObjects is flexible enough to suit the needs of any web programmer. You can, for instance, write code using one of three programming languages: Java, Objective-C, or WebScript. You can write simple WebObjects applications in a matter of minutes. And if your programming task is more complex, WebObjects still makes it as easy as possible by performing common web application tasks automatically and by allowing you to reuse objects you’ve written for other applications.
This book describes concepts that you’ll need to know when writing a WebObjects application. Programmers of all skill levels will find all or part of the information in this book useful. To help you find what you are looking for, this book is organized into three parts:
Part 1, “WebObjects Essentials,” is for programmers who are new to WebObjects.
Part 1 covers basic concepts that are required knowledge for even the simplest of WebObjects programs. It describes what a WebObjects application is and what pieces a WebObjects application contains. It describes how to debug applications and provides a checklist of hard­to-find errors. The final chapter in Part 1, “WebObjects Viewed Through Its Classes,” provides an in-depth description of the classes used in all WebObjects applications and explains how those classes process HTTP requests.
Part 2, “Special Tasks,” is for intermediate-to-advanced WebObjects programmers.
Part 2 provides information that you can ignore until you understand the basic WebObjects concepts explained in Part 1. It describes how you can design components for reuse inside of other components, how a WebObjects application manages and stores state, how to create
11
Introduction
client-side Java components that behave like dynamic elements, and how to design an application for deployment and improved performance.
Part 3, “WebScript,” is for programmers who want to use a scripting language.
WebScript is a scripting language provided with WebObjects for rapid application development. Use of WebScript is entirely voluntary—you can write applications using Java or Objective-C if you prefer. Part 3 describes WebScript’s syntax and also describes how to use the Foundation framework when writing an application using WebScript.
There are no prerequisites for learning WebObjects; however, it does help if you understand object-oriented programming concepts and are familiar with either Java or Objective-C. If you aren’t familiar with Java or Objective-C, you might want to pay special attention to Part 3 of this book. Part 3 provides a very brief introduction to object-oriented concepts—enough to get you started. Later, you’ll want to round out your knowledge either by reading the book
Oriented Programming and the Objective-C Language
or any other book on object-
Object-
oriented programming.
Other Useful Documentation
If you’re new to WebObjects programming, begin by reading the book Getting Started With WebObjects
a WebObjects application as well as the basic concepts behind WebObjects.
If you want to write an application that accesses a database, you’ll need to use Enterprise Objects in conjunction with WebObjects. And although database access is covered in the tutorials in also want to read the depth information.
Other valuable information can be found online. To access online documentation, use the WebObjects Home Page. The WebObjects Home Page is in your web server’s document root, and you can access it at this URL:
http://localhost/WebObjects/Documentation/WOHomePage.html
In particular, WOHomePage’s Documentation link gives you access to some books that are available only online:
12
. It contains tutorials that teach the mechanics of creating
Getting Started With WebObjects, you’ll probably
Enterprise Objects Framework Developer’s Guide for more in-
Other Useful Documentation
WebObjects Tools and Techniques describes the development tools WebObjects Builder and Project Builder and shows how to use them to create WebObjects applications.
Serving WebObjects describes how to administer and deploy WebObjects
• applications after you’ve written them.
The WebObjects Class Reference provides a complete reference to the classes in the WebObjects framework. Reference material is provided for both the Java and Objective-C languages.
The
Dynamic Elements Reference documents the dynamic elements
provided with WebObjects and shows examples of how to use them.
The Client-Side Applet Controls Reference lists and describes the client- side Java components provided with WebObjects.
13
WEBOBJECTS ESSENTIALS
Chapter 1
What Is a WebObjects Application?
WebObjects is a product that makes it easy for you to write dynamic web­based applications (or
WebObjects applications). Before you start programming, however, you need to understand what a WebObjects application is.
This chapter answers the question what is a WebObjects application in two ways: first by showing you the pieces of a simple WebObjects application, and then by explaining what happens when a WebObjects application runs. In the rest of Part 1, you’ll learn how to construct these pieces and you’ll get more in-depth information about how they work.
Read this chapter if you want a very high-level overview. The rest of this book provides much more detailed information about how WebObjects applications work and how to write one.
When you’re ready to start programming, read the book
WebObjects
. It provides a series of tutorials that help you understand the tasks
and tools involved in writing a WebObjects application.
The Ingredients of a WebObjects Application
WebObjects applications reside within a directory named web server’s document root (
<DocumentRoot>
/WebObjects/Examples/WebScript
These are WebObjects application projects, provided with the WebObjects package as examples that you can use when learning WebObjects. These examples range from simple to highly complex. For now, you might want to focus on two of the simplest applications, named
When you look inside any of these project directories, you may see these pieces:
directories, which are called components. Components are dynamic
.wo
HTML pages.
An application code file (
applicationwide resources.
•A session code file (
resources.
<DocumentRoot>
Application.wos
Session.wos
), which creates and manages sessionwide
Getting Started With
/WebObjects
WebObjects
). Look in
in your
, and you’ll see several directories.
HelloWorld
and
Visitors
.
), which creates and manages
Standard project files, such as makefiles.
19
Chapter 1 What Is a WebObjects Application?
The following sections describe the WebObjects application ingredients in more detail.
Components
To write a WebObjects application, you create components and then connect them. A behavior. Usually a component represents an entire page, so the word “page” is used interchangeably with the word “component.” However, remember, that not all components represent an entire page. For example, a component might represent only a header or footer of a page, and you can nest that component inside of a component that does represent the entire page.
component is a web page, or a portion of one, that has both content and
Each component is located in its own directory, named
Component
generally contains these parts:
•A template that specifies how the component looks
Code that specifies how the component acts
Bindings that associate the component’s template with its code
Main.wo
Figure 1 shows the contents of the
is almost always the name of the first page of a WebObjects
example. (
Main.wo
application.) In this example, the template in the form of an HTML file ( declarations file (
Main.wod), which contains the bindings between the template
component from the HelloWorld
Main.wo
component contains three files: a
), the code file (
Main.html
Main.wos
and the code.
Main.wo
Main.html
Main.wod Main.wos
, and
.wo
), and the
Figure 1. The Contents of a WebScript Component Directory
Typically, components contain some form of the three files shown in Figure 1; however, any given component might contain more or fewer files. For example, components whose code is written in a compiled language do not contain code
20
The Ingredients of a WebObjects Application
files. A component may not need a code file at all; it may need only a template file and a declarations file. Another component might have a code file but no template file or declarations file. Plus, if you create a component using Project Builder or WebObjects Builder, you’ll get a fourth file,
Component
, which contains API that should be made public to other
.api
components.
The next three sections describe more completely these template, code, and declarations files.
Template
You use a template ( look. This file typically contains static HTML elements (such as <P>) along with some dynamic elements. Dynamic elements are the basic building blocks of a WebObjects application. They link an application’s behavior with the HTML page shown in the web browser, and their contents are defined at runtime.
An HTML template can also contain a reference to another component (called a
reusable component or subcomponent) that represents a portion of an HTML page. This reference behaves just like a reference to a dynamic element.
Main.html
) to specify how the page you’re creating should
<H1> or
Code or Script File
You use the code file ( actions. The attributes are called actions are called
With WebObjects, you can write your code file in one of three programming languages: Java, Objective-C, or WebScript. Java is the language of choice for many people; others prefer Objective-C. Because both of these languages require compiling, they aren’t as well suited to rapid prototyping as a scripting language is. For this reason WebObjects provides a scripting language named WebScript, described in the chapter “The WebScript Language” (page 163). You may have noticed that the examples directory mentioned previously offers examples in all three languages.
Java support is not available on the Mach or HP-UX platform.
Note:
The
Main.wo
component shown in Figure 1 uses a WebScript file to define its behavior. (The or Objective-C, the code file resides at the same level as the
Main.wos
) to define your component’s attributes and
variables or instance variables, and the
methods.
extension signifies WebScript.) If you want to use Java
.wos
Main.wo directory
21
Chapter 1 What Is a WebObjects Application?
as shown in Figure 2. (In Project Builder, Java and Objective-C code files are shown under Classes instead of with the component under Web Components.)
HelloWorld
Main.wo
Main.html
Figure 2. Location of Code File for Java Component
Main.wod
Main.java
You can mix languages. It’s common to use WebScript to write your interface logic (that is, the files described in this chapter) and use Java or Objective-C to write your business logic. Many simple applications are written entirely in WebScript. Some programmers prototype using WebScript and then create a compiled version of the same application to improve performance.
Bindings
You use a declarations file (Main.wod) to define the bindings, or mapping, between the methods and variables you defined in your code and the dynamic elements in your template. For example in the HelloWorld application, the HTML template for the Main component contains two dynamic elements. The declarations file specifies that the first dynamic element represents a text field whose value maps to the user types a name in the text field, WebObjects assigns it to the variable. The declarations file also specifies that the second dynamic element is a submit button and that when the user clicks the button, WebObjects invokes
sayHello method.
the
visitorName variable in the component’s script. When the
visitorName
22
The Ingredients of a WebObjects Application
Application Code
In addition to having one or more components, your application can also include application code. In application code, you declare and initialize application variables and perform tasks that affect the entire application.
The application code file is named Application and its extension is based on the programming language you use to create it (
Application.m).
Application.wos, Application.java, or
Session Code
Sessions are periods during which one user is accessing your application. Because users on different clients may be accessing your application at the same time, a single application may have more than one session accessing it at a time (see Figure 3). Each session has its own copy of the components that its user has requested.
component
session
application
Figure 3. Application, Sessions, and Components
You perform tasks that affect a single session and store variables that persist throughout a session in the session code file. The session code file is named Session and has the appropriate extension (
Session.wos, Session.java, or Session.m).
A Note on WebObjects Classes
True to its name, WebObjects is an object-oriented environment for writing web applications. Therefore, when you write a component, an application file, or a session file, you are really writing a class. This is true whether you use WebScript, Java, or Objective-C. You can learn about these classes and
23
Chapter 1 What Is a WebObjects Application?
where they are used by reading the chapter “WebObjects Viewed Through Its Classes” (page 63).
Components are subclasses of a class named WOComponent. For example, in Figure 1 the component directory creates a WOComponent subclass named Main. Application files create subclasses of a class named WOApplication, and session files create subclasses of a class named WOSession.
WOComponent, WOApplication, and WOSession are defined, along with other classes, in the WebObjects Framework in
NeXT_ROOT
/NextLibrary/Frameworks/WebObjects.framework. (NeXT_ROOT is an
environment variable defined at installation time. On Windows NT systems, it
C:\NeXT by default. On Mach systems, the NeXT_ROOT environment variable is
is undefined, but you can think of it as being the root directory
In Java, WebObjects classes have different names. The names shown previously are the WebScript names. (Objective-C uses the same names as WebScript.) In Java, WOComponent is called Component, WOApplication is WebApplication, and WOSession is WebSession. The Java classes are contained in the package
next.wo.
Note: This book generally uses the WebScript names for classes and methods.
Usually, you can easily discern the Java name from the WebScript name, and vice versa. The following table tells you how to do so.
/.)
WebScript/Objective-C Name Java Name
Class names WOClass Class (or next.wo.Class)
Zero-argument methods
Single-argument methods method: method
Multiple-argument methods
method method
methodWithArg1
:arg2: methodWithArg1
Where the mapping is not obvious, this book notes both the Java and WebScript/Objective-C names.
Application Directory
When you build a WebObjects application project, the result is a directory with the extension the component files, copies of the script files (if any), and any resources that the application or the HTTP server needs access to. When you’re ready to deploy an application to your users, you use the
24
.woa. This directory contains the application executable, copies of
.woa to run the application.
Running a WebObjects Application
Now you’ve learned what a WebObjects application looks like and seen the pieces that you’ll have to write. The next section tells you how to run a WebObjects application.
Running a WebObjects Application
WebObjects applications run on a web server. Your users connect to a WebObjects application using web browsers that they run on their own (client) machines. How does a user start a WebObjects application, and how does the application communicate with the browser?
Users run a WebObjects application using a Uniform Resource Locator (URL) similar to the one shown in Figure 4. (Of course, you’d probably provide a button or a link on a static web page that would take users to this URL rather than forcing your users to type such a long string.)
http://sonora/cgi-bin/WebObjects/Examples/HelloWorld
Web server
host name
Figure 4. A URL to Start a WebObjects Application
Name of the
Web server’s
cgi-bin directory
WebObjects
adaptor name
The WebObjects application
directory in
<DocumentRoot>
To start your own applications, you open a command shell window, go to the directory that contains your application, and enter the application command. WebObjects starts up your application, opens the web browser, and enters the URL in the web browser for you. For example, to start the Java version of HelloWorld, go to the directory
<DocRoot>
the executable file, and enter
/WebObjects/Examples/Java/HelloWorldJava/HelloWorldJava.woa, which contains
HelloWorld on the command line. On Windows
NT, you can simply navigate to this directory in the Explorer and double­click the
HelloWorld.exe file.
When you run a WebObjects application, it communicates with the web browser through the chain of processes shown in Figure 5.
25
Chapter 1 What Is a WebObjects Application?
Web
Browser
Figure 5. Chain of Communication Between the Browser and Your WebObjects Application
HTTP
Server
WebObjects
Adaptor
WebObjects
Application
Here is a brief description of these processes:
An HTTP server. Any HTTP server that uses the Common Gateway Interface
(CGI), the Netscape Server API (NSAPI), or the Internet Server API (ISAPI).
A WebObjects adaptor. A WebObjects adaptor connects WebObjects applications
• to the web by acting as an intermediary between web applications and HTTP servers.
A WebObjects application executable. The application executable receives incoming
• requests and responds to them, usually by returning a dynamically generated HTML page.
Two of these, WebObjects adaptors and WebObjects application executables, are described next.
WebObjects Adaptors
A WebObjects adaptor receives requests from the server, repackages the requests in a standard format, and forwards them to an appropriate WebObjects application (see Figure 6).
HTTP
Server
Figure 6. The Role of a WebObjects Adaptor
Server
Interface
WebObjects
Adaptor
WebObjects
Interface
WebObjects
Application
All WebObjects adaptors communicate with WebObjects applications in the same way, but they communicate with HTTP servers using whatever interface is provided by a particular server. For example, the WebObjects CGI adaptor uses the Common Gateway Interface, the Netscape Interface adaptor uses the Netscape Server API (NSAPI), and the Internet Server adaptor uses the
26
Running a WebObjects Application
Internet Server API (ISAPI). Thus, WebObjects adaptors can take advantage of server-specific interfaces but still provide server independence.
By default, WebObjects uses the WebObjects CGI adaptor. The Common Gateway Interface is supported by all HTTP servers, so you can use the CGI adaptor with any server—including those that are publicly available. As demands on performance increase, switch to one of the other adaptors with a server that supports the corresponding API (Netscape Server API or Internet Server API). Such servers are capable of dynamically loading the adaptor, eliminating the overhead of starting a new process for each request. As shown in Figure 7, the communication between the adaptor and the HTTP server occurs inside a single process.
Netscape
Commerce
Server
Figure 7. The Netscape Interface Adaptor
Netscape
Interface
Adaptor
WebObjects
Interface
WebObjects
Application
The online document Serving WebObjects describes how to configure a WebObjects adaptor.
The WebObjects Application Executable
An application executable is an executable file, provided by you or by WebObjects, that receives incoming requests from the adaptor and responds to them, usually by returning a dynamically generated HTML page.
If your application is written entirely in WebScript, it can use the default application executable, NeXT_ROOT provided as part of the WebObjects package. If your application contains compiled code, you build your own executable and use it in place of
WODefaultApp.
WebObjects applications are event driven, but instead of responding to mouse and keyboard events, they respond to HTTP requests. A WebObjects application receives a request, responds to it, and then waits for the next request. The application continues to respond to requests until it terminates. During each cycle of this request-response loop, the application extracts the user input from the request, invokes an action if one is
/NextLibrary/Executables/WODefaultApp,
27
Chapter 1 What Is a WebObjects Application?
associated with the user’s action, and generates a response—usually an HTML page (see Figure 8).
Web Browser HTTP Server WebObjects Adaptor WebObjects Applications
Request Page
Request
Request
1. Take Values From Request
Request Component
User performs an action
Response Page
User sees the next page
Response
Figure 8. The Request-Response Loop
Response
2. Invoke Action
3. Generate Response
Returns response component
Response Component
Generates response page
28
Chapter 2
Dynamic Elements
In the previous chapter, you learned that a WebObjects application is made up of components, which in turn are made up of dynamic elements. Dynamic elements are the basic building blocks of a WebObjects application. They link an application’s behavior with the HTML page shown in the web browser, and their contents are defined at runtime.
There are two types of dynamic elements that you can place in a component:
Server-side dynamic elements
Client-side Java components
This chapter describes each of these types and tells you how to decide when to use them. Before reading it, you should be familiar with the concepts presented in the previous chapter. To learn the mechanics of using dynamic elements, see the online book WebObjects Tools and Techniques.
Server-Side Dynamic Elements
Server-side dynamic elements are the simplest type of element to create and are supported by all web browsers. Needless to say, they are the most commonly used.
Server-side dynamic elements produce HTML at runtime. This HTML is composed of the same HTML elements you use when you’re creating a static web page. Like static elements, dynamic elements display formatted text, images, forms, hyperlinks, and active images. WebObjects provides several dynamic elements. For a complete list, see the online book Dynamic Elements Reference.
For an example of dynamic elements in action, look at the CyberWind sample application. It’s located in where
<DocRoot>
is your web server’s document root. When you run
<DocRoot>
/WebObjects/Examples/Java/CyberWindJava,
CyberWind, its first page contains a list of hyperlinks, shown in Figure 9.
Figure 9. CyberWind Main Page
31
Chapter 2 Dynamic Elements
This list is not hard-coded into the page. Instead, it is produced by several dynamic elements. Figure 10 shows how this same part of the page looks in WebObjects Builder.
Figure 10. CyberWind Main Page in WebObjects Builder
The elements shown in Figure 10 are a WORepetition, a WOHyperlink, and a WOString. The WORepetition element corresponds to a That is, it iterates through a list of items and, for each item in that list, prints its contents. In this example, the contents are a WOHyperlink and a WOString. The WOHyperlink is a hyperlink whose destination is determined at runtime, and the WOString is a string whose contents are determined at runtime.
When you run CyberWind, the WORepetition walks through an array of strings that the component’s code supplies. For each item in the array, it displays a hyperlink whose text is the text of the string item in the array. In this array, there are two strings—“See surfshop information” and “Buy a new sailboard”—so the WORepetition creates two hyperlinks, each containing the appropriate text.
for loop in C code.
As the name implies, server-side dynamic elements operate entirely on the server (see Figure 11). That is, when a server-side dynamic element is asked to draw itself, it returns HTML code that should form part of a page, the page is constructed, and then the entire page is sent from the server to the client. Later in this chapter, you’ll learn about client-side components, which transport values and state from the server to the client and then draw themselves on the client machine.
32
Server-Side Dynamic Elements
Response PageWeb Browser
<HTML>
</HTML>
ServerClient
Dynamic Element
<...>
Dynamic Element
<...>
Dynamic Element
<...>
Figure 11. Server-Side Dynamic Elements
How Server-Side Dynamic Elements Work
To learn how server-side dynamic elements work, look once more at the Main page of the CyberWind example in WebObjects Builder. If you switch over to raw mode, you see that the Main page contains this HTML code in its template:
Choose between the following menu options:<BR><BR> <WEBOBJECT NAME="OPTION_REPETITION">
<WEBOBJECT NAME="OPTION_LINK">
<WEBOBJECT NAME="OPTION_NAME"></WEBOBJECT>
</WEBOBJECT>
</WEBOBJECT>
Each WEBOBJECT tag denotes the position of a dynamic element. Notice that the tag specifies only where the dynamic element should go; it does not specify the dynamic element’s type. The type is specified in the
OPTION_REPETITION:WORepetition {
list = allOptions;
item = currentOption }; OPTION_LINK:WOHyperlink {
action = pickOption }; OPTION_NAME:WOString {
value = currentOption };
.wod file:
33
Chapter 2 Dynamic Elements
In the .wod file, each element is identified by name and then its type is specified. The outermost dynamic element in the HTML file (OPTION_REPETITION) defines a WORepetition, the next element is a WOHyperlink, and the innermost element is a WOString.
Each type specification is followed by a list of attributes. Dynamic elements define several attributes that you bind to different values to configure the element for your application. Usually, you bind attributes to variables or methods from your component’s code (see Figure 12).
TEXTFIELD : WOTextField { value = aName; }
Button : WOSubmitButton { action = recordThis; }
id aName;
- recordThis{ ... }
Record
Figure 12. Dynamic Element Bindings
action
value
In the CyberWind example, the Main component binds to two attributes of WORepetition: WORepetition should iterate over. The
list and item. The list attribute specifies the list that the
item attribute specifies a variable whose
value will be updated in each iteration of the list (like an index variable in a loop). CyberWind’s Main component binds the
allOptions and the item attribute to a variable named currentOption.
list attribute to an array named
for
The WOHyperlink has an
pickOptions. The action attribute specifies a method that should be invoked when
the user clicks the link. In this case, the
action attribute, which is bound to a method called
pickOptions method determines which link
the user clicked and then returns the appropriate page.
34
Server-Side Dynamic Elements
Finally, the WOString element defines a value attribute, which specifies the string you want displayed. This variable, which is also bound to the you’ll recall,
currentOption is updated with each iteration that the
WORepetition makes. So, for each item in the WORepetition’s
list attribute), the WORepetition updates the currentOption
value attribute is bound to the currentOption
item attribute of the WORepetition. As
allOptions array (assigned to the
variable to point to that item and then the WOString prints it on the page.
Binding Values to Dynamic Elements
In the CyberWind example, all of the dynamic elements are bound to variables and methods from the component that contains them (the Main component). It’s common to bind to variables and methods declared directly in the current component; however, you can bind to any value that the component can access.
This means, for instance, that you can bind to variables from the application or session object because the WOComponent class declares two instance variables, current session. Look at CyberWind’s Footer component in WebObjects Builder. This component displays, among other information, the date and time the CyberWind application was started.This date is stored in the application object, not in the Footer component. The Footer component’s
.wod file contains this declaration:
application and session, which point to the current application and the
UP_SINCE:WOString {value = application.upSince.description};
To retrieve a value from this binding, WebObjects uses key-value coding, a standard interface for accessing an object’s properties either through methods designed for that purpose or directly through its instance variables. With key-value coding, WebObjects sends the same message
takeValue:forKey:, or takeValue in Java) to any object it is trying to access. Key-
( value coding first attempts to access properties through accessor methods based on the key’s name.
For example, to resolve the binding for the WOString element in the Footer component using key-value coding, WebObjects performs the following steps:
It resolves the value for the
application in the component object.
In this case, WOComponent (Component in Java) defines the
application key by looking for a method named
application
method, which returns the WOApplication object (WebApplication in Java).
35
Chapter 2 Dynamic Elements
It resolves the value for the upSince key by looking for a method named upSince in the application object.
If the method is not found, it looks for an case, the
It resolves the value for the
description in the upSince object.
Because
upSince instance variable is defined in the application’s code file.
description key by looking for a method named
upSince is a date object, it defines a description method, which prints
upSince instance variable. In this
the object’s value as a string.
Note: The Java equivalent of the description method is toString, but you must use
the WebScript name for methods and literals in the
.wod file even though
the application is written in Java.
Here are the general rules for binding dynamic element attributes:
•You must bind to a variable or method accessible by the current component. (You can also bind to constant values.)
If you bind to a method, the method must take no arguments. (If you need to bind to a method that takes arguments, you can wrap it inside of a method that doesn’t take arguments.)
•You can bind to any key for objects that define keys.
For example, dictionary objects store key-value pairs. Suppose you declare
person dictionary that has the keys name, address, and phone. These keys aren’t
a really instance variables in the dictionary, but because WebObjects accesses values using key-value coding, the following binding works:
myString : WOString { value = person.name };
•You must use the Objective-C names for methods and literals.
Even if your entire application is written in Java, you must use the Objective-C names for methods and for literals. For example, you must use
YES instead of true, NO instead of false, and description instead of toString.
Declarations File Syntax
As you’ve seen, the .wod file specifies nearly all of the information necessary to create a dynamic element. Because you usually create dynamic elements and their bindings using WebObjects Builder, you normally don’t have to worry about the syntax of the
36
.wod file. However, here it is for the curious:
Client-Side Java Components
elementName
Notice that the last attribute/value pair before a closing brace (}) does not end with a semicolon (;).
As described in the previous section, value can be a constant, variable, or method. It can also be a string of messages joined by a dot, similar to the Java syntax for sending messages but without the parentheses. For example:
application.upSince.description
Client-Side Java Components
Instead of using server-side dynamic elements, you can create Java applets that run on the client. Typically, applets are downloaded to the client once and then have virtually no communication with the server. This is not the case with the client-side Java components provided with WebObjects. While these applets run on the client, they continuously synchronize their states with objects on the server. Client-side components can also trigger action methods on the server. For this reason, they may be said to work in virtually the same way as server-side dynamic elements.
To add a client-side component to your application, you drag it from the palette provided in WebObjects Builder. For a list of the client-side Java components that WebObjects provides, see the online book Client-Side Applet Controls Reference.
:
elementType
{
attribute
=
value;attribute
=
value
};
Deciding When to Use Client-Side Components
You should use client-side components whenever you want greater control over the appearance of your application. In general, client-side components have these advantages over server-side dynamic elements:
Client-side components define a state-synchronization phase that does not reload the page.
As you learned in the first chapter, WebObjects applications are event driven. The events that trigger actions are HTTP requests. A WebObjects application receives an HTTP request from the client, processes it, and returns a response page. That is, the only communication that takes place between the client and the WebObjects application on the server results in a page being redrawn (or a new page being generated).
37
Chapter 2 Dynamic Elements
When client-side components are used, an HTTP request can result in either the resynchronization of state or the return of a new page. Thus, state can be synchronized without the page having to be redrawn (see Figure 13).
Client-Side
Web Browser
HTML
Figure 13. Client-Side Java Components
Component
Association
attributes
WOApplet
Dynamic Element
ServerClient
Client-side components are more flexible than server-side dynamic elements.
Server-side dynamic elements always generate HTML, which means that they are limited to what HTML looks like and what HTML can do. You can create client-side components that look like just about any imaginable control: a dynamic calendar, a spreadsheet, or a graphing tool. To learn how to create a client-side component, see the chapter “Creating Client-Side Components” (page 141).
The disadvantage to using client-side components is that they require a Java­enabled browser. Thus, you can use client-side components only when you can be certain all of your users will have Java-enabled browsers. If you can’t guarantee this, you should use server-side dynamic elements.
How Client-Side Components Work
A client-side component is really just a special case of a particular server-side dynamic element named WOApplet. You use WOApplet when you want to include any Java applet in a WebObjects application. The difference between a client-side component and other Java applets is that client-side components can communicate with the server.
38
Client-Side Java Components
When you look at client-side component’s bindings in the .wod file, it looks like this example:
INPUTFIELD : WOApplet {
code = "next.wo.client.controls.TextFieldApplet.class"; codebase = "/WebObjects/Java"; archive = "woextensions.jar"; width = "200"; height = "20"; associationClass = "next.wo.client.SimpleAssociation"; stringValue = inputString
};
Like any other server-side dynamic element, the WOApplet’s definition contains a list of attributes bound to constants or variables in the component’s code.
code attribute specifies which client-side component this WOApplet
The should download. The
codebase attribute specifies the path of the component
relative to your web server’s document root. (For the provided client-side components, this path is always
The
archive attribute specifies .jar files that should be preloaded onto the
/WebObjects/Java.)
client machine. If you don’t use this attribute, the applet downloads Java
.class files from the server one by one as it needs them. With the archive
attribute, you can package all necessary Java classes into archive files, and they are downloaded once. However, only web browsers that have Java 1.1 support can use your users use browsers that don’t support
.jar files. Because Java 1.1 is fairly new, there’s a good chance
.jar files. All of the provided
client-side components are packaged in a single archive file named
woextensions.jar.
The
associationClass attribute differentiates the client-side components from
any other applet you might include in your application. This attribute specifies an object (a subclass of
next.wo.client.Association) that the component
uses to communicate with the application on the server. The Association object can get and set component state and cause methods to be invoked in the server when actions are triggered in the client. For the WebObjects­provided client-side components, this attribute is always
next.wo.client.SimpleAssociation. If you create your own client-side components,
you provide your own Association subclass.
The final attribute, component. The Association object assigns the value of the
stringValue, is an attribute specific to the TextFieldApplet
inputString
variable to be the value of the text field on the client and keeps the two objects in sync so that they always have the same value.
39
Chapter 3
Common Methods
The methods that you write for your WebObjects application provide the behavior that makes your application unique. Because you are writing subclasses of WOApplication, WOSession, and WOComponent (in Java, WebApplication, WebSession, and Component), you inherit the methods provided by those classes. These inherited methods take care of the details of receiving HTTP requests and generating responses. However, you’ll sometimes find that you need to override some of the inherited methods to perform certain tasks.
This chapter describes the types of methods that you generally write in a WebObjects application. These types are:
Action methods
Initialization and deallocation methods
Request-handling methods
In cases where you override existing methods, those methods are invoked at standard, predictable times during the application’s request-response loop (the main loop for a WebObjects application). For background on the request-response loop, see the chapter “WebObjects Viewed Through Its Classes” (page 63).
As you’re writing methods, refer to the class specifications for WOApplication, WOSession, and WOComponent to learn which messages you can send to these objects. The class specifications are in the online book WebObjects Class Reference.
Action Methods
An action method is a method you associate with a user action—for instance, clicking a submit button, an active image, or a hyperlink. To associate your method to a user action, you map it to a dynamic element that has an attribute named
action. (In the examples just given, the dynamic elements
associated with the user actions are WOSubmitButton, WOActiveImage, or WOHyperlink.) When the user performs the associated action, your method is invoked.
For example, in the HelloWorld example application (in
<DocRoot>
/WebObjects/Examples/WebScript/HelloWorld, where
<DocRoot>
is your web
server’s document root), the submit button is mapped to a method named
sayHello in the Main component. When users see this page, they type in a
43
Chapter 3 Common Methods
name and click the button. This initiates the application’s request-response loop, and
sayHello is invoked.
Action methods take no arguments and return a page (component) that will be packaged with an HTTP response. For example, the new page named Hello and sends that page the name the user has typed into the text field.
//WebScript HelloWorld Main.wos
- sayHello {
id nextPage; nextPage = [WOApp pageWithName:@"Hello"]; [nextPage setVisitorName:visitorName]; return nextPage;
}
If you’re programming in Java, you can look at the HelloWorldJava example, which is identical to HelloWorld but written in Java. Its this:
//Java HelloWorld Main.java public Component sayHello() {
Hello nextPage = (Hello)application().pageWithName("Hello"); nextPage.setVisitorName(visitorName); return nextPage;
}
In this example, the component Main is used to generate the page that handles the user request, and the component Hello generates the page that represents the response. Main is the request component or the request page, and Hello is the response component or the response page.
sayHello method creates a
sayHello method looks like
It’s common for action methods to determine the response page based on user input. For example, the following action method returns an error page if the user has entered an invalid part number (stored in the variable
partnumber); otherwise,
it returns an inventory summary:
// WebScript example
- showPart {
id errorPage; id inventoryPage;
if ([self isValidPartNumber:partnumber]) {
errorPage = [[self application] pageWithName:@"Error"]; [errorPage setErrorMessage:@"Invalid part number %@.",
partnumber];
return errorPage; } inventoryPage = [[self application] pageWithName:@"Inventory"]; [inventoryPage setPartNumber:partnumber]; return inventoryPage;
}
44
Action Methods
// Java example public Component showPart() {
Error errorPage; Inventory inventoryPage;
if (isValidPartNumber(partNumber)) {
errorPage = (Error)application().pageWithName("Error"); errorPage.setErrorMessage("Invalid part number " +
partnumber);
return errorPage; } inventoryPage = (Inventory)
application().pageWithName("Inventory"); inventoryPage.setPartNumber(partnumber); return inventoryPage;
}
Action methods don’t have to return a new page. They can instead direct the application to use the request page as the response page by returning
null in Java). For example, in the Visitors application, the recordMe action
(
nil
method in the Main page records the name of the last visitor, clears the text field, and redraws itself:
// WebScript Visitors Main.wos
- recordMe {
if ([aName length]) {
[[self application] setLastVisitor:aName];
[self setAName:@""]; // clear the text field }
}
// Java Visitors Main.java public Component recordMe {
if (aName.length != 0) {
((Application)application()).setLastVisitor(aName);
aName = ""; // clear the text field } return null;
}
Note: Always return nil (null) in an action method instead of returning self (this).
Returning Returning
nil uses the request component as the response component. self uses the current component as the response component. At
first glance, these two return values seem to do the same thing. However, if the action method is in a component that’s nested inside of the request component, a return value of
self will make the application try to use the
nested component, which represents only a portion of the page, as the response component. This, most likely, is not what you want. Therefore, it is safer to always return
nil.
45
Chapter 3 Common Methods
Initialization and Deallocation Methods
Like all objects, WOApplication, WOSession, and WOComponent implement initialization methods (or constructors in Java). Because most subclasses require some unique initialization code, these are the methods that you override most frequently. In WebScript, the initialization methods are initialization methods are the constructor for the class and
Both
init and awake perform initialization tasks, but they are invoked at different
times during an object’s life. The once, when the object is first created. In contrast, of each cycle of the request-response loop that the object is involved in. Thus, it may be sent several times during an object’s life.
init and awake. In Java, the
awake.
init message (or the constructor in Java) is sent
awake is sent at the beginning
Complementing
awake and init are the sleep and dealloc methods. These methods
let objects deallocate their instance variables and perform other clean-up tasks.
sleep method is invoked at the end of each cycle of the request-response
The loop, whereas the
The
dealloc method is used primarily for Objective-C objects. Standard dealloc
methods in Objective-C send each instance variable a
dealloc method is invoked at the end of the object’s life.
release message to make
sure that the instance variables are freed. WebScript and Java, because they have automatic garbage collection, usually make a deallocation method unnecessary. If you find it necessary, you can implement
finalize in Java.
dealloc in WebScript and
The Structures of init and awake
The init method must begin with an invocation of super’s init method and must end by returning
- init {
}
Likewise, in Java, the constructor must begin with an invocation of the superclass’s constructor (as with all Java classes):
public Application() {
}
The awake method has no such structure. In it, you don’t need to send a message
super or return anything.
to
self.
[super init]; /* initializations go here */ return self;
super(); /* initializations go here */
46
Initialization and Deallocation Methods
- awake { /* initializations go here */
}
public void awake () {
/* initializations go here. */
}
Application Initialization
The application init method is invoked only once, when the application is launched. You perform two main tasks in the application’s
Initialize application variables
Configure applicationwide settings
init method:
For example, the Visitors application has this
// WebScript Visitors Application.wos
- init { [super init]; lastVisitor = @""; [self setTimeOut:7200]; return self;
}
// Java Visitors Application.java public Application () {
super(); ... lastVisitor = ""; setTimeOut(7200); ...
}
init method in Application.wos:
This method begins by calling the application’s init method. Then, it initializes the application variable
lastVisitor to be the empty string. (The
application has just started, so there has been no last visitor.) Finally, it sets the application to terminate after it has been running 2 hours.
This example sets the application time-out value. You might want to do other configurations in the application object’s
init method as well. For
example, you can control how pages and components are cached and how state is stored. For more information, read the chapter “Managing State” (page 109).
The application’s request-response loop. Therefore, in the
awake method is invoked at the start of every cycle of the
awake method, you perform
anything that should happen before each and every user request is processed. For example, the DodgeDemo example application keeps track of the number of requests the application has received. It increments and logs that number at the top of the request-response loop:
47
Chapter 3 Common Methods
// WebScript DodgeDemo Application.wos
- awake { ++requestCount; [self logWithFormat:@"Now serving request %@", requestCount];
}
// Java DodgeDemo Application.java public void awake() {
++requestCount; this.logString("Now serving request " + requestCount);
}
Session Initialization
A session object is created each time the application receives a request from a new user. An application may have multiple sessions running concurrently. The session ends when a session time-out value is reached.
In the session object’s
init method, you set the session’s time-out value and
initialize variables that should have unique values for each session. For example, in the CyberWind application, each session keeps track of which number it is. These values are changed in the session object’s
//From CyberWind Session.wos
- init { [super init]; [self setTimeOut:120]; // session idle time is 2 minutes. [[self application] setSessionCount:[[self application]
sessionCount + 1]; sessionNumber = [[self application] sessionCount]; return self;
}
//From CyberWindJava Session.java public Session() {
super(); Application application = (Application)application(); this.setTimeOut(120); application.setSessionCount(application.sessionCount() + 1); sessionNumber = application.sessionCount();
}
init method:
The session object’s awake method is invoked each time the user associated with the session makes a new request. After the application object has performed its
awake method, it restores the appropriate session object and sends it the
own
awake message too.
The CyberWind application keeps track of the number of requests per session. It increments the number in the session’s
awake method.
- awake { requestCount++;
}
48
Initialization and Deallocation Methods
Component Initialization
A component object’s init method is invoked when the component object is created. Just how often a particular component object is created depends on whether the application object is caching pages. For more information, see “WebObjects Viewed Through Its Classes” (page 63). If page caching is turned on (as it is by default), the application object generally creates the component object once and then restores that object from the cache every time it is involved in a user request. If page caching is turned off, the component object is freed at the end of the request-response loop.
Note: The pageWithName: method shown in the section “Action Methods”
(page 43) always creates a new component object, even if page caching is turned on.
A component object’s For example, in the EmployeeBook example, the
init to initialize the departments component variable:
// WebScript EmployeeBook Department.wos id departments;
- init { id departmentsPath;
[super init]; departmentsPath = [[self application]
pathForResourceNamed:@"Departments" ofType:@"array"]; departments = [NSArray arrayWithContentsOfFile:departmentsPath]; return self;
}
init method usually initializes component variables.
Department.wos script uses
The component awake method is invoked immediately after the init method and each time the component object is restored from the page cache. Just
init, you can implement an awake method that initializes component
as in variables. For example, in the DodgeDemo application, the
awake to initialize the shoppingCart component variable:
uses
// WebScript DodgeDemo Car.wos
- awake { shoppingCart = [[self session] shoppingCart];
}
Car.wos script
In general, you use init to initialize component instance variables instead of
awake. The reason is that init is invoked only at component initialization time,
whereas
awake is potentially invoked much more than that. If, however, you
want to minimize the amount of state stored between cycles of the request­response loop, you might choose to initialize component instance variables
awake and then deallocate them in sleep (by setting them to nil in WebScript
in
49
Chapter 3 Common Methods
or null in Java). For more information, see the chapter “Managing State” (page 109).
Request-Handling Methods
Request-handling is performed in three phases, which correspond to three methods that you can override:
•Taking input values from the request (
takeValuesFromRequest)
Invoking the action (
Generating a response (
invokeActionForRequest:inContext: or invokeAction)
appendToResponse:inContext: or appendToResponse)
takeValuesFromRequest:inContext: or
Each of the methods is implemented by WOApplication, WOSession, and WOComponent. In each phase, WOApplication receives the message first, then sends it to the WOSession, which sends it to the WOComponent, which sends it to all of the dynamic element and component objects on the page.
The request-handling methods handle three types of objects:
•A request object (WORequest or Request in Java) is passed as an argument in the first two phases. This object represents a user request. You can use it to retrieve information about the request, such as the method line, request headers, the URL, and form values.
•A context object (WOContext or Context in Java) is passed as an argument in all three phases. This object represents the current context of the application. It contains references to information specific to the application, such as the path to the request component’s directory, the version of WebObjects that’s running, the application name, and the request page’s name.
•A response object (WOResponse in Java) is passed in the final phase. This object encapsulates information contained in the generated HTTP response, such as the status, response headers, and response content.
You should override these methods if you need to perform a task that requires this type of information or you need access to objects before or after the action method is invoked. For example, if you need to modify the header lines of an HTTP response or substitute a page for the requested page, you would override
appendToResponse:inContext:.
50
Request-Handling Methods
As you implement request-handling methods, you must invoke the superclass’s implementation of the same methods. But consider where you invoke it because it can affect the request, response, and context information available at any given point. In short, you want to perform certain tasks before
super is invoked and other tasks after super is invoked.
Taking Input Values From a Request
The takeValuesFromRequest:inContext: method is invoked during the first phase of the request-response loop, immediately after all of the objects involved in the request have performed their concludes, the request component has been initialized with the bindings made in WebObjects Builder.
awake methods. When this phase
Override
takeValuesFromRequest:inContext: when you want to do one of the
following:
Access information from the request or context object.
Perform postprocessing on user input.
In the first case, you can place your code before the message to second case, you must place your code after the message to example, the following implementation of
takeValuesFromRequest:inContext:
super. In the
super. For
records the kinds of browsers—user agents—from which requests are made:
// WebScript example
- takeValuesFromRequest:request inContext:context { id userAgent = [request headerForKey:@"user-agent"]; [self recordUserAgent:userAgent]; [super takeValuesFromRequest:request inContext:context];
}
The following example performs postprocessing. It takes the values for the
street, city, state, and zipCode variables and stores them in the address variable
formatted as a standard mailing address.
// WebScript example
- takeValuesFromRequest:request inContext:context {
[super takeValuesFromRequest:request inContext:context]; address = [NSString stringWithFormat:@"%@\n%@, %@ %@",
street, city, state, zipCode];
}
// Java example public void takeValuesFromRequest(Request request, Context context) {
super.takeValuesFromRequest(request, context); address = street + city + state + zipCode;
}
51
Chapter 3 Common Methods
Invoking an Action
The second phase of the request-response loop involves
invokeActionForRequest:inContext:
object until it is handled by the dynamic element associated with the user action (typically, a submit button, a hyperlink, and active image, or a form).
invokeActionForRequest:inContext: if you want to return a page other than the one
Use requested. This scenario might occur if the user requests a page that has a dependency on another page that the user must fill out first. The user might, for example, finish ordering items from a catalog application and want to go to a fulfillment page but first have to supply credit card information.
. WebObjects forwards this method from object to
The following example, implemented in
Session.wos, returns a “CreditCard” page
if the user hasn’t supplied this information yet:
// WebScript example
- invokeActionForRequest:request inContext:context { id creditPage; id responsePage = [super invokeActionForRequest:request
inContext:context];
id nameOfNextPage = [responsePage name];
if ([self verified]==NO &&
[nameOfNextPage isEqual:@"Fulfillment"]) { creditPage = [[self application]
pageWithName:@"CreditCard"]; [creditPage setNameOfNextPage:nameOfNextPage]; return creditPage;
} return responsePage;
}
//Java example public Element invokeActionForRequest(Request request, Context contenxt) {
Component creditPage; Component responsePage = super.invokeActionForRequest(request,
context);
String nameOfNextPage = responsePage.name();
if (verified()==false &&
(nameOfNextPage.compareTo("Fulfillment") == 0) { creditPage = application().pageWithName("CreditCard"); creditPage.setNameOfNextPage(nameOfNextPage); return creditPage;
} return responsePage;
}
When the application receives a request for a new page (say, a fulfillment page), the session object determines whether or not the user has supplied valid credit­card data by checking the value of its
verified variable. If the value of verified is NO,
the session object returns the “CreditCard” component. As shown in the
52
Request-Handling Methods
following action method, the “CreditCard” component sets the verified session variable to YES when the user has supplied valid credit information and returns the user to the original request page to try again.
- verifyUser { if ([self isValidCredit]) {
[[self session] setVerified:YES];
return [[self application] pageWithName:nameOfNextPage]; } return nil;
}
Limitations on Direct Requests
Users can access any page in an application without invoking an action. All they need to do is type in the appropriate URL. For example, you can access the second page of HelloWorld without invoking the opening this URL:
http://serverhost/cgi-bin/WebObjects/Examples/HelloWorld.woa/-/Hello.wo/
When a WebObjects application receives such a request, it bypasses the user-input (
invokeActionForRequest:inContext:) phases because there is no user input to store
(
takeValuesFromRequest:inContext:) and action-invocation
and no action to invoke. As a result, the object representing the requested page—Hello in this case—generates the response.
sayHello action by
By implementing security mechanisms in
invokeActionForRequest:inContext:, you
can prevent users from accessing pages without authorization, but only if those pages are not directly requested in URLs. To prevent users from directly accessing pages in URLs, you must implement another strategy.
Generating a Response
The appendToResponse:inContext: method is invoked in the final phase of the request-response loop, during which the application generates HTML for the response page. You can override this method to add to the response content or otherwise manipulate the HTTP response. For example, you can add or modify the HTTP headers as in the following example:
- appendToResponse:aResponse inContext:aContext {
[super appendToResponse:aResponse inContext:aContext]; [aResponse setHeader:@"True"
}
In a similar manner, you can use appendToResponse:inContext: to add text to the response content. In the following example, a component’s
forKey:@"dshttpd-NoAutomaticFooter"];
53
Chapter 3 Common Methods
appendToResponse:inContext: method adds bold and italic markup elements around a
string’s value as follows:
id value; id escapeHTML; id isBold; id isItalic;
- appendToResponse:aResponse inContext:aContext {
id aString = [value description];
[super appendToResponse:aResponse inContext:aContext]; [aResponse appendContentHTMLAttributeValue:@"<p>"]; if (isBold) {
} if (isItalic) {
}
if (escapeHTML) {
} else {
}
if (isItalic) {
} if (isBold) {
}
}
[aResponse appendContentHTMLAttributeValue:@"<b>"];
[aResponse appendContentHTMLAttributeValue:@"<i>"];
[aResponse appendContentString:aString];
[aResponse appendContentHTMLString:aString];
[aResponse appendContentHTMLAttributeValue:@"</i>"];
[aResponse appendContentHTMLAttributeValue:@"</b>"];
After you invoke super’s appendToResponse:inContext:, the application generates the response page. At this point you could do something appropriate for the end of the request. For example, the following implementation terminates the current session:
public void appendToResponse(response, context) {
super.appendToResponse(response, context); session().terminate();
}
For more details on each phase of the request-response loop, read the chapter “WebObjects Viewed Through Its Classes” (page 63).
54
Chapter 4
Debugging a WebObjects Application
In the previous chapters, you learned the pieces of a WebObjects application and the kinds of methods you need to write. Once you’ve put together an application, you should debug it to make sure it runs properly. The techniques you use to debug vary according to the languages you’ve used to write the application.
This chapter describes how to debug WebScript code, Java code, and Objective-C code in a WebObjects application. When you debug, you’ll be using the Project Builder application. To learn how to use Project Builder, see the online book WebObjects Tools and Techniques.
Before you debug, it’s a good idea to test your installation and verify that it works properly. If you haven’t already done so, follow the instructions in the online document Post-Installation Information.
Launching an Application for Debugging
You debug WebObjects applications using Project Builder, as described in the online book WebObjects Tools and Techniques. The executable you launch differs based on which language you used to write the application. This section tells you how to begin a debugging session for WebObjects applications written in each of the three available languages: WebScript, Java, and Objective-C.
Debugging WebScript
To debug WebScript code, you rely on log messages and trace statements described in the section “Debugging Techniques” (page 58).
If you’ve written an application entirely in WebScript, you typically debug it by running NeXT_ROOT Builder launch panel, as described in WebObjects Tools and Techniques. When you do, the output from the debugging and trace statements is displayed in the launch panel.
/NextLibrary/Executables/WODefaultApp from the Project
Debugging Java
The debugging strategy for Java applications is very similar to the strategy for debugging WebScript applications. Because the WebObjects Java bridge is incompatible with WebObjects. Instead, you can use the methods described in the section “Debugging Techniques” (page 58) as well as Build the executable for your project using Project Builder, then launch that
jdb, no Java debugger is supported for
System.out.println statements.
57
Chapter 4 Debugging a WebObjects Application
executable in the launch panel. Output from the debugging methods appears in the launch panel.
Debugging Objective-C
If all or part of your application is written in Objective-C, you can use the gdb debugger in Project Builder. For more information on debugging an Objective­C application with Project Builder, see Project Builder’s online help.
If your application contains WebScript code as well as Objective-C code, you debug the WebScript portion using statements as described in “Debugging Techniques” (page 58).
Debugging Mixed Applications
When you build a WebObjects application project, the result is a .woa file package inside of the project directory. You may notice that this file package contains all of the application’s components (including scripted components), and all other resources need to run the application, as well as the application executable itself.
When you’re debugging an application, the executable uses the components from the project directory instead of those in the ignore the components placed inside of the make a change, change the component in the project directory. When you run an application, it checks to see if its (that is, a directory that contains a file named takes its scripted components from the project directory. This way, you can make any necessary changes to your scripts in Project Builder, and (once you have saved the scripts) your application automatically picks up your changes without your having to rebuild.
logWithFormat: and WOApplication trace
.woa package, so you can safely
.woa package. When you need to
.woa package is inside of a project directory
PB.project). If it is, the application
Debugging Techniques
To debug WebScript and Java code, you rely primarily on log messages and trace statements that write to standard output. This section describes the statements you can include in your code to help you debug.
Writing Debug Messages
The method logWithFormat: (in Java, logString) writes a formatted string to standard error (
58
stderr).
Debugging Techniques
In WebScript and Objective-C, logWithFormat: works like the printf() function in C. This method takes a format string and a variable number of additional arguments. For example, the following code excerpt prints the string “The value of myString is Elvis”:
myString = @"Elvis"; [self logWithFormat:@"The value of myString is %@", myString];
When this code is parsed, the value of myString is substituted for the conversion specification data type of the variable being substituted is an object (that is, of the
%@. The conversion character @ indicates that the
id data
type).
Because in WebScript all variables are objects, the conversion specification you use must always be specifications for primitive C data types such as
%@. Unlike printf(), you can’t supply conversion
%d, %s, %f, and so on. (If you
do, you might see the address of the variable rather than its value.)
In Java, the equivalent of WebApplication objects. Instead of using
logWithFormat: is logString, and you can send it only to
printf specifications, it uses
concatenation. Here’s how you’d write the same lines of code in Java:
myString = "Elvis"; application().logString("The value of myString is " + myString);
Perhaps the most effective debugging technique is to use logWithFormat: to print the contents of variables. For example, this statement at the end of the HelloWorld’s
[self logWithFormat:@"The contents of self in sayHello are %@", self];
self. This prints the values of all of your component
sayHello method in
Main.wos:
produces output that resembles the following:
The contents of self in sayHello are <<WOScriptedClass(/WebObjects/Examples/WebScript/HelloWorld.woa/Main .wo/Main): 0x8cb08 name=Main subcomponents=0x0> visitorName=frank>
Here’s how you’d write the same line of code in Java:
application().logString("The contents of this in sayHello are "
+ this.toString());
Using Trace Methods
WOApplication (in Java, WebApplication) provides trace methods that log different kinds of information about your running application. These
59
Chapter 4 Debugging a WebObjects Application
methods are useful if you want to see all or part of the call stack. The following table describes the trace methods:
Method Description
– trace: Enables all tracing.
– traceAssignments: Logs information about all assignment statements.
– traceStatements: Logs information about all statements.
– traceScriptedMessages: Logs information when an application enters and exits a scripted method.
– traceObjectiveCMessages: Logs information about all Objective-C methods invocations.
The output from the trace methods appears in Project Builder’s launch panel.
You use the trace methods wherever you want to turn on tracing. Usually, you do this in the
init method (or constructor) of a component or the application:
- init { [super init]; [self.application traceAssignments:YES]; [self.application traceScriptedMessages:YES]; return self;
}
Isolating Portions of a Page
If a component is producing unexpected HTML output, you can try to isolate the problem by printing small portions of the page at a time. Use HTML comments (
<!--) to comment out all but the suspect portion of the page and
reload the component. Verify that this portion works as you intend it to. Reduce the size of the commented out portion of the page until more and more of the page is visible in the browser. Continue until you have found the offending area.
Programming Pitfalls to Avoid
This section describes some things to look out for as you debug your application.
WebScript Programming Pitfalls
Because WebScript looks so much like Objective-C and C, it’s easy to forget that WebScript isn’t either of these languages. When you’re debugging WebScript code, watch out for the following tricky spots:
60
Programming Pitfalls to Avoid
•WebScript supports only objects that inherit from NSObject. As most
objects inherit from NSObject, this limitation is easy to overlook. Notably, EOFault does not inherit from NSObject, so you cannot use it in WebScript code.
The == operator is supported only for NSNumber objects. If you use
== to compare two objects of any other class, the operator compares the addresses of the two objects, not the values of the two objects. To test the equality of two objects, use the
NSString *string1, *string2;
// WRONG! if (aString1 == aString2) ...
// Right if ([aString1 isEqual:string2]) ...
isEqual: method.
The postincrement and postdecrement operators are not supported. If
you use them, you won’t receive an error message. Instead, they behave like preincrement and predecrement operators.
i = 0; if (i++ < 1 )
// This code never gets executed.
•WebScript always evaluates both sides of a Boolean expression (such as
&& and ||). You should make sure that the second half of an expression does not produce an error.
// WRONG! produces a divide by 0 if a is 0. if ((a == 0) || (b / a) > 5) ...
For more information, see the chapter “The WebScript Language” (page
163).
Java Programming Pitfalls
When debugging Java code, watch out for the following tricky spots:
•You can’t define multiple constructors or overloaded methods for the
classes WebApplication, WebSession, Component, or any other class that originates as an Objective-C class. For example, the following code causes your application to crash:
public class MyComponent extends Component {
public void myMethod() { .... }
//WRONG! Overloaded method causes runtime error. public void myMethod(int anInt) { ... }
}
61
Chapter 4 Debugging a WebObjects Application
The pageWithName method creates the page by looking up and instantiating the component class that has the same name as the argument you provide
pageWithName. For this reason, your subclass of Component shouldn’t be
to given a package name. For example, if you create a component named
MyPage.wo and place its Java file in the package myClasses.web, pageWithName won’t
find the
MyPage.class file.
Java is a more strictly typed language than is Objective-C or WebScript. If you’re more familiar with Objective-C, you’ll find that you need to cast the return types frequently. For example, suppose you define a method named
verify in the file Session.java and you want to invoke that method from a
component’s Java file. To do so, you must cast the return type of the component’s
// From a component’s Java file. ((Session)session()).verify();
session method as in the following:
By definition, session returns a WebSession object. Because WebSession does not define a method named cast the return value of
verify, your code won’t compile unless you
session to your WebSession subclass.
62
Chapter 5
WebObjects Viewed Through Its Classes
The Classes in the Request-Response Loop
As you learned at the end of the first chapter, WebObjects applications respond to HTTP requests from the server and return responses in the form of dynamically generated HTML pages. The main loop of a WebObjects application, in which the application performs this work, is called the request-response loop. You have a very broad understanding of how this works: the web browser sends a request to the HTTP server, which forwards it to the WebObjects adaptor, which translates it into a form that a WebObjects application can understand. For the response, the process is reversed.
This chapter describes in much greater detail what happens during the request-response loop. It does so by describing the request-response loop as WebObjects views it: as a communication between objects. In this chapter, you learn about the objects that are involved at each level of the loop, each object’s duty during each part of the request-response loop, and the way these objects generate an appropriate HTML page in response to the user request.
In the chapter “Common Methods” (page 41), you learned some of the methods that are invoked during the request-response loop, and you learned about cases where you might want to override these methods. As you write more complex WebObjects applications, it becomes necessary to know exactly what happens at each point in the processing of an HTTP request and the generation of an HTTP response. You should read this chapter to learn that level of detail. You can also refer to the class specifications in the online book WebObjects Class Reference.
The Classes in the Request-Response Loop
The request-response loop begins when an incoming message (URL) from a client web browser is handled by the HTTP server. This section starts at that point and then dives into the request-response loop layer by layer, telling you which classes get involved, and at which point. Later sections walk you through the sequence of events that happen during one cycle of the request-response loop and the sequence of events for generating an HTML page.
Server and Application Level
At the server and application level, the request-response loop looks like
that shown in Figure 14.
65
Chapter 5 WebObjects Viewed Through Its Classes
Request
HTTP server
Figure 14. Request-Response Loop: Application and Server Level
adaptor
application
Response
The HTTP server sends a request to the application’s adaptor. The adaptor packages the incoming HTTP request in a form the WebObjects application can understand and forwards it to the application. The application initiates and manages the process of request handling and returns the completed response to the adaptor, which gives it to the HTTP server in a form the server can understand.
Two classes are involved at this level:
WOAdaptor (in Java, Adaptor)
Defines the interface for objects mediating the exchange of data between an HTTP server and a WebObjects application. This is an abstract class.
WOApplication (in Java, WebApplication)
Receives requests from the adaptor and initiates and coordinates the request-handling process, after which it returns a response to the adaptor. WOApplication also creates dynamic elements “on the fly” and manages adaptors, sessions, application resources, and components.
Session Level
At the session level, the request-response loop looks like that shown in Figure 15.
66
The Classes in the Request-Response Loop
Request
session
store
HTTP server
Figure 15. Request-Response Loop: Session Level
adaptor
Response
application
session 1
session 2
The objects dedicated to session management ensure that state with sessionwide scope persists between cycles of the request-response loop.
Two classes are involved at this level:
WOSession (in Java, WebSession)
Encapsulates the state of a session. WOSession objects persist between the cycles of the request-response loop. WOSession objects store (and restore) the pages of a session, the values of session variables, and any other state that components want to persist throughout a session. The number of pages stored by the session object is dependent on the page-cache size set in WOApplication. Setting the page-cache size is described in the chapter “Managing State” (page 109). Each session object is identified by a unique session ID, which is reflected in the URL.
WOSessionStore (in Java, SessionStore)
Provides the strategy or mechanism through which WOSession objects are made persistent. A WOSessionStore object stores session objects in the server or in the page (which can include Netscape cookies), and restores them upon request by the application.
67
Chapter 5 WebObjects Viewed Through Its Classes
When a user makes an initial request to a WebObjects application, the application creates a session object (WOSession). At the end of the request­response cycle, the application stores the state-bearing session object using the facilities of WOSessionStore. With each subsequent cycle of the request­response loop for that user, the application restores the state of the session at the beginning of the cycle and stores it again at the end of the cycle. To learn more about how to use WOSessionStore, see the chapter “Managing State” (page 109).
Request Level
The request-response cycle has three phases, the first for transferring user­entered data to the objects associated with the request page, the second for invoking an action method, and the third for generating and returning the response. Figure 16 shows how WebObjects requests are handled at the transaction level.
Request
HTTP server
Figure 16. Request-Response Loop: Transaction Level
adaptor
Response
application
transaction 1
transaction 2
Three classes are involved at this level:
WORequest (in Java, Request)
Stores essential data about an HTTP request, such as header information, form values, HTTP version, host and page name, and session, context, and sender IDs.
session store
session 1
session 1
68
The Classes in the Request-Response Loop
WOResponse (in Java, Response)
Stores and allows the modification of HTTP response data, such as header information, status, and HTTP version. It also provides convenience methods for appending HTML and simple textual data to the content of the response (that is, the response page).
WOContext (in Java, Context)
Provides access to the objects involved in the current cycle, such as the current request, response, session, and application objects. It also stores the component (either the current page or one of its subcomponents) to which the elements of the page make reference when they “push and pull” values through association. See “How HTML Pages Are Generated” (page 82) for an explanation. The WOContext object acts as a “cursor,” traversing the object graph during each phase of the request-response loop. The WOContext for a cycle is identified by a unique context ID, which appears in the URL.
You rarely need to work directly with WORequest, WOResponse, and WOContext yourself. At the beginning of the request-response loop, the WOAdaptor and WOApplication objects create instances of these three classes. The application initiates each phase of the request-response loop by sending the messages
invokeActionForRequest:inContext:, and appendToResponse:inContext: (in Java, takeValuesFromRequest, invokeAction, and appendToResponse). It passes in the
takeValuesFromRequest:inContext:,
WORequest, WOResponse, and WOContext objects as arguments to one or more of these methods. From these objects, the components, dynamic elements, and other objects involved in the cycle get essential information. See “How WebObjects Works—A Class Perspective” (page 72) for more on the mechanics of request handling.
Page Level
At the page level, objects of many classes (most of them private) work together to compose the HTML content of response pages (see Figure 17). Many of the same objects also set their variable values from data entered into request pages and respond to user actions.
69
Chapter 5 WebObjects Viewed Through Its Classes
HTTP server
adaptor
Response
Request
application
session store
transaction 1
request
page
transaction 2
request
page
session 1
response
page
response
page
session 2
Figure 17. Request-Response Loop: Page Level
Two major branches of these objects descend from WOElement: WOComponent objects, which represent components, and WODynamicElement objects, which represent dynamic HTML elements on the page. For details on how this happens and for more on these classes, see “How HTML Pages Are Generated” (page 82).
Four classes are involved at this level:
WOComponent (in Java, Component)
Represents a integral, reusable page (or portion of a page) for display in a web browser.
WOElement (in Java, Element)
Declares the three request-handling methods:
invokeActionForRequest:inContext:, and appendToResponse:inContext:. WOElement is an
takeValuesFromRequest:inContext:,
abstract class. Each node in an object graph, which represents the HTML elements of a component and their relationships, is an object that inherits from WOElement.
WODynamicElement (in Java, DynamicElement)
An abstract class for subclasses that generate particular dynamic elements.
70
The Classes in the Request-Response Loop
WOAssociation (in Java, Association)
Knows how to find and set a value by reference to a key. Instance variables and action methods of dynamic elements are instances of this class.
Database Integration Level
Database integration is handled mainly by classes in the Enterprise Objects Framework (see Figure 18). The Enterprise Objects Framework converts operations on objects to database operations on records, thereby allowing your WebObjects application to interact with a database in an object­oriented manner.
Request
session store
HTTP server
HTTP serverHTTP server
adaptor
application
request 1
session 1
Response
Figure 18. Request-Response Loop: Database Access
Two classes are involved at this level:
WODisplayGroup (in Java, DisplayGroup)
Performs fetches, queries, creations, and deletions of records from one table in the database. WODisplayGroup is a sort of bridge between the
request
page
WODisplay
Group
EOEditing
Context
response
page
71
Chapter 5 WebObjects Viewed Through Its Classes
dynamic elements on your page and the objects in the Enterprise Objects Framework.
EOEditingContext (in Java, next.eo.EditingContext)
Manages a graph of objects fetched from a database. The objects represent tables, rows, and columns in the database.
When a WebObjects application accesses a database, one or more of the components in the application contain one or more WODisplayGroup objects. The session object provides access to an EOEditingContext object that is used, for example, when changed data is saved to the database. Each session uses an EOEditingContext to manage graphs of objects fetched from a database and to ensure that all parts of an application remain synchronized. For read-only applications, you can customize WOSession to return a per-application EOEditingContext.
For more information on how the WebObjects and Enterprise Objects classes interact, see the Enterprise Objects Developer’s Guide.
How WebObjects Works—A Class Perspective
You’ve now had a brief introduction to the classes used in WebObjects. This section describes the sequence of events that happen during a cycle of the request-response loop—how the application starts up, what happens when it receives an HTTP request, and how it processes that request.
Starting the Request-Response Loop
A WebObjects application can start up in one of two ways: automatically, when it receives a request (autostarting), or manually, when it’s run from the command line. Either way, its entry point is the same as that of any C program: the function. In a WebObjects application,
main is usually very short. Its job is to
create and run the application.
main function begins by creating an autorelease pool that’s used for the
The automatic deallocation of objects that receive an
autorelease message. It then calls
a function that loads the Java Virtual Machine (VM) if necessary.
The next step is to create a WOApplication (or WebApplication) object. This seems fairly straightforward, but in the
init method or constructor the application
creates and stores, in an instance variable, one or more adaptors. These adaptors, all instances of a WOAdaptor subclass, handle communication between an
72
main
How WebObjects Works—A Class Perspective
HTTP server and the WOApplication object. The application first parses the command line for the specified adaptors (with necessary arguments); if none are specified, as happens when the application is autostarted, it creates a suitable default adaptor.
run method initiates the request-response loop. When run is invoked,
The the application sends
registerForEvents to each of its adaptors to tell them to
begin receiving events. Then the application begins running in its run loop.
The autorelease pool is released and recreated immediately before the
run
message is sent. Releasing the autorelease pool at this point releases any temporary variables created during initialization of the application class. Creating a new autorelease pool before sending
run ensures that all variables
created while running the application will be released. The last message releases the autorelease pool, which in turn releases any object that has been added to the pool since the application started running.
In the rest of this section, we look at what happens during one complete cycle of the request-response loop.
Taking Values From the Request
The first phase of the request-response loop (see Figure 19) synchronizes the state of the request component with the HTML page as submitted by the user. In this phase, the appropriate dynamic elements extract the values that users enter and the choices they make in the request page and assign them to declared variables.
For example, if the user clicked a checkbox, the dynamic element that represents that checkbox must be set to the “checked” state. In other words, the element must be set to YES.
checked attribute of the appropriate WOCheckbox dynamic
73
Chapter 5 WebObjects Viewed Through Its Classes
Application
init awake
Create WOSession or Restore WOSession Create page or Restore page
takeValuesFrom Request:inContext:
Session
init awake
takeValuesFrom Request:inContext:
Gets request page and stores reference to it.
Request Page
init awake
takeValuesFrom Request:inContext:
Gets template for page.
Page Template
takeValuesFrom Request:inContext:
Each dynamic element of the the template that accepts input responds to takeValuesFrom Request:inContext:. If a user-entered value belongs to any element, the element sets the related value of the related WOAssociation.
Figure 19. Taking Values From the Request
A cycle of the request-response loop begins when the WOAdaptor receives an incoming HTTP request. The adaptor object packages this request in a WORequest and forwards this object to the application object in a
handleRequest:
message. Upon receiving this message, the application object does the following:
1. It creates the WOResponse and WOContext objects that will be needed.
2. It invokes its own
awake method.
3. It determines which session and which request page are associated with the request, as described next.
Accessing the Session
The application determines whether to create a new session or access an existing session by searching the request URL (which was passed in as an argument to the one for the session, the request URL looks like the URL shown in Figure 20.
74
handleRequest: method) for a session ID. If the request is the first
How WebObjects Works—A Class Perspective
HTTP
server name
adaptor
name
http://ursa/cgi-bin/WebObjects/SomeWebApp
name of the Web server's
cgi-bin directory
Figure 20. URL to Start a WebObjects Application
application path relative to
<
DocRoot
>/ WebObjects
This URL does not contain a session ID, so the application object creates a new session by performing the following steps:
1. It sends itself a
2. As part of the
createSession message.
createSession method, it sends the init message or the
constructor message to the WOSession (or WebSession) class to create a new session object.
3. It sends the
awake message to the session object.
If the request is part of an existing session, the request URL looks like the one shown in Figure 21.
application
active
element
ID
application
instance number
server name
HTTP server name
http://ursa/cgi-bin/WebObjects/SomeWebApp.woa/19335471518261838039837077512/Main.wo/62793212911/0.1.0/-/ursa
adaptor
name
application
name
secure
session
ID
page
name
context
ID
Figure 21. WebObjects URL in an Existing Session
This URL contains all of the information necessary to restore the state of the existing session. The session ID comes right after the application name in the URL. Because sessions are designed to protect the data of one user’s
75
Chapter 5 WebObjects Viewed Through Its Classes
transactions from that of another, session IDs must not be easily predicted or faked. To this end, WebObjects uses randomly generated 32-digit integers as session IDs. (You can also override WOSession’s another security scheme if you’d like.)
The application keeps existing, active sessions in the WOSessionStore object. The application object uses the session ID to retrieve the appropriate session from the session store (see Figure 22). The appropriate session object is then sent the
awake message to prepare it for the request.
http://ursa/cgi-bin/WebObjects/CyberWind.woa/193354715182610083803983707751271/Main.wo/62793212911/0.1.0/-/ursa
sessionID method and implement
session
store
115182610354478370775108387039932
193354715182610083803983707751271
298371518295496879423495129469577
121458576092359769455670990532240
HTTP server "ursa"
WebObjects
applications
"CyberWind"
session
session
session
session
Figure 22. Associating a Request With a Session Object
Creating or Restoring the Request Page
After the session receives the awake message, the next step is to find the request page. Each request received by a WebObjects application is associated with one
of the application’s pages—the request page. The request page is usually the response page from the last request. (The response page shows the result, or output, of the request.)
If the user has just begun a new session (that is, if the request URL looks like the one shown in Figure 20), the user has not requested a specific page. Therefore, the application object creates a new instance of the WOComponent
76
How WebObjects Works—A Class Perspective
class for the page named “Main.” The application object performs the following steps to create a component:
1. It looks in the runtime system for a WOComponent subclass that has the same name as the request page (in this case, “Main”). If it finds such a class with the same name, it creates an instance of that class.
2. If the application object fails to find a class in the runtime system, it looks for a scripted component with the name of the request page. When it finds the
.wo directory, it creates a component object using a
unique WOComponent subclass for the scripted component and makes the scripted code the class implementation.
3. It invokes the WOComponent subclass’s
4. It invokes the WOComponent subclass’s
init method or constructor.
awake method to prepare it for
the request.
If the request is made from an established session, the application object attempts to retrieve the request page from its cache. By default, an application caches component (or page) instances once they’re created, primarily to facilitate backtracking: when users backtrack, they’re revisiting pages restored by the application. The request URL contains the information needed to retrieve the page from the cache (see Figure 21). This information includes the page name and a context ID.
The component may not be in the cache for one of three reasons:
The page-caching feature is turned off.
The request is the first for that page during the session.
The user has backtracked beyond the page cache limit.
If the component is not in the cache, the application object creates the component using the procedure described above. If the component is in the cache, it sends the component the
awake message.
Note that to retrieve the page from the cache, a context ID is required in addition to the page name. The context ID identifies a page as it existed at the end of a particular request-response loop. Why is the context ID necessary? Imagine you’re accessing a WebObjects application that lets you subscribe to various publications. You navigate from the site’s home page to the order page, where you select a publication, and then you go to the customer information page and fill in your address. After submitting this information, you navigate back to the home page. Next, you decide to enter a
77
Chapter 5 WebObjects Viewed Through Its Classes
subscription for a friend. You follow the process a second time, selecting a different publication and entering your friend’s address.
At this point, within a single session with the subscriptions application, you’ve accessed the same pages twice, entering different information each time. Let’s say that you now realize that you made a mistake in your own address, so you backtrack to that page, change the address, and resubmit the information. It’s important that the new address information is submitted to the customer information page as it existed during the first order so that the revised information can be associated with the right publication order.
WebObjects associates a different context ID (again, a randomly generated integer—to maintain security) with each request-response loop cycle. A request to a session includes both the name of request page and a context ID so the session object can locate, from its cache of page instances, the appropriate one to handle the request.
Assigning Input Values
At this point, the application, session, and component objects have been created (if necessary) and awakened so that they are ready for the request. The next step is to extract user-entered values and assign them to variables. Here is the basic sequence of events in preparing for a request:
1. The application object sends
takeValuesFromRequest) to itself; its implementation simply invokes the session
object’s
takeValuesFromRequest:inContext: method.
2. The session sends the
takeValuesFromRequest:inContext: (in Java,
takeValuesFromRequest:inContext: message to the request
component.
3. The component, in its implementation of
takeValuesFromRequest:inContext:, gets its
template and forwards the message to the template’s root object. A template is an object graph that represents the static HTML elements, dynamic HTML elements, and subcomponents that together compose the page associated with a component instance.
4. All dynamic elements in the page template and in the templates of subcomponents receive the
takeValuesFromRequest:inContext: message. If one of
these elements “owns” a user-entered value, it responds to the message by storing the value in the appropriate variable defined in the request component’s declarations file.
78
How WebObjects Works—A Class Perspective
For more on how components are associated with templates, and on how HTML elements participate in request-handling, see “How HTML Pages Are Generated” (page 82).
Invoking an Action
In the second phase of the request-response loop (see Figure 23), the application first determines which dynamic element the user has clicked (or otherwise activated) and then has that element trigger the appropriate action method in the request component. This method returns the response page—the component responsible for generating an HTTP response. If the user has not triggered an action, the request component is used as the response component.
Application
invokeActionFor Request:inContext:
response page
Figure 23. Invoking an Action
Session
invokeActionFor Request:inContext:
Gets request page and stores reference to it.
Request Page
invokeActionFor Request:inContext:
Gets template for component
Invokes action method, which returns component of response page.
Page Template
invokeActionFor Request:inContext:
Target dynamic element responds by returning value of action.
Here is the basic sequence of events for invoking an action:
1. The application object sends
invokeAction) to itself; its implementation simply invokes the session
object’s
2. The session sends
invokeActionForRequest:inContext: method.
invokeActionForRequest:inContext: to the request component.
3. The component, in its implementation of
invokeActionForRequest:inContext: (in Java,
invokeActionForRequest:inContext:,
gets the template of the component and forwards the message to the template’s root object.
4. Suitable dynamic elements in the request-page template and in subcomponent templates handle the
invokeActionForRequest:inContext:
79
Chapter 5 WebObjects Viewed Through Its Classes
message and invoke the appropriate action method in the request component. This action method returns the response page.
To be suitable, an element must be able to respond to user actions (a WOSubmitButton or a WOActiveImage, for example). Each of these elements evaluates the invoked action to determine if it “owns” it.
For more on how components are associated with templates and on how HTML elements participate in request-handling, see “How HTML Pages Are Generated” (page 82).
Generating the Response
In the final phase of request-response loop (see Figure 24), the response page generates an HTTP response. Generally, the response contains a dynamically generated HTML page. Each element (static and dynamic) that makes up the response page appends its HTML code to the total stream of HTML code that will be interpreted by the client browser.
Application
appendToResponse: inContext:
Completes response
Saves session
sleep ’ ’ ’ dealloc
Session
appendToResponse: inContext:
Gets response page and stores reference to it.
saves page.
sleep ’
’ ’ ’ ’ dealloc
appendToResponse:
inContext:
Response Page
Gets template for page
Sends sleep to all "
awake
components ’
’ ’ ’ ’
dealloc
"
Page Template
appendToResponse: inContext:
Dynamic and static HTML elements append their content to response.
Figure 24. Generating the Response
Here is the basic sequence of events for generating a response:
80
How WebObjects Works—A Class Perspective
1. The application object stores the response component indicated by the action method’s return value. (This action method was invoked during the second phase of the request-response loop.)
2. If the response component is different from the request component, application sends the
awake message to the response component.
3. The application object sends
appendToResponse:inContext: to itself; its
implementation simply invokes the session object’s
appendToResponse:inContext: method.
4. The session pushes the response component onto the WOContext stack and sends the response component the
appendToResponse:inContext:
message.
5. The response component, in its implementation of
appendToResponse:inContext:, gets the template for the component and sends appendToResponse:inContext:
to the template’s root object.
6. All static and dynamic HTML elements in the response-page template, and in subcomponent templates, receive the
appendToResponse:inContext: message. In it, they append to the content of the
response the HTML code that represents them. For dynamic elements, this code includes the values assigned to variables.
7. When control returns to the session object, the session object asks the WOStatisticsStore to record statistics about the response. WOStatisticsStore sends the session a
descriptionForResponse:inContext:
message. The session, in turn, sends the response component
descriptionForResponse:inContext: message. By default, this method returns the
response component’s name.
After the response has been generated, but before returning the response to the adaptor, the application object concludes request handling by doing the following:
1. It causes the
sleep method—the counterpart of awake—to be invoked in
all components involved in the cycle (request, response, and subcomponents). As described in the chapter “Managing State” (page 109), in the
sleep method, objects can release resources that don’t
have to be saved between cycles.
2. It requests the session object to save the response page in the page cache.
3. It invokes the session object’s
sleep method.
81
Chapter 5 WebObjects Viewed Through Its Classes
4. It saves the session object in the session store.
5. It invokes its own
When an Objective-C object is about to be destroyed, its invoked at an undefined point in time after a cycle (indicated by the vertical ellipses in Figure 24). In the instance variables. In WebScript, this usually happens implicitly; you therefore usually don’t need to implement the Java, objects have automatic garbage collection, so this deallocation step is unnecessary.
How HTML Pages Are Generated
So how exactly are request-handling messages propagated from a component to its HTML elements? To answer this, we must understand the relationship between a component and an HTML element.
Both components and HTML elements (static and dynamic) share a common ancestor, WOElement (in Java, Element). WOElement declares, but does not implement, the three request-handling messages:
invokeActionForRequest:inContext:, and appendToResponse:inContext:. This common
inheritance, of course, makes it possible for both components and HTML elements to participate in request handling. But there the inherited similarities end. Although components can generate HTML content, this capability is not an essential characteristic, as it is with objects on the other branch of the inheritance tree.
sleep method.
dealloc method is
dealloc method, the object releases any retained
dealloc method in any objects you write. In
takeValuesFromRequest:inContext:,
Component Templates
The first step to generating a component’s HTML page is to create a template for the component. This template is not the same as the HTML template discussed in the chapter “What Is a WebObjects Application?” (page 17). In this context, a template is a graph of WOElement and WOComponent objects created by parsing and integrating the component’s
25). The network of references corresponds to locations on the page and to parent-child relationships; for instance, a WOForm element would probably have WOTextField and WOSubmitButton children.
82
.html and .wod files (see Figure
How HTML Pages Are Generated
page (component)
page template
Figure 25. An Object Graph for a Page’s Template
The template is created at runtime when the component is first requested. The template is part of a larger component definition, which also includes information that allows instances of this component to share resources. Instances carry only the instance-variable values that are distinctive to them; the rest is stored in the component definition. You can, if you wish, enable caching of component definitions so that the component is parsed only once during an application’s lifetime. To do so, send the application object a
setCachingEnabled: message in its initialization method.
For each request-handling message, WOComponent’s default behavior is to forward the message to the objects in its template. To do so, it first retrieves the template from the component definition. The component definition returns the WOElement object at the root of the object graph. This root object, in turn, forwards the message to each of its child elements; if they have any children, these elements send the message to them. Thus, each element has, if appropriate, an opportunity to extract user data from the request, to invoke an action in the component, and to append its HTML representation to the response.
Each HTML element on a template has an element ID to identify it within the object graph. An element ID is implemented as an extension of the sender ID in the URL. You can request the current element ID from the WOContext object.
83
Chapter 5 WebObjects Viewed Through Its Classes
Associations and the Current Component
A dynamic HTML element, such as a text field or a pop-up button, differs from a static HTML element, such as a heading, in that its attributes can change over a cycle of the request-response loop. These attributes can include values that determine behavior or appearance (a “disabled” attribute, for instance), values that users enter into a field, values that are returned from a method, and actions to invoke when users click or otherwise activate the element. Each dynamic element stores its attributes as instance variables of type WOAssociation (in Java, Association). WOAssociation objects know how to obtain and set the value they represent. They generally do this using key-value coding.
The key to a value can be represented as a sequence of keys separated by periods. The resolution of a key by yielding its value makes possible the resolution of the next key. For instance:
self.aRepetition.list.item
means that self (identifying the current component) has a WORepetition named
aRepetition. The list key denotes the list of elements displayed by the
WORepetition, and the (including actions) are WOAssociations defined for each dynamic element. The values for these keys are constants assigned in the bindings to variables, to methods, or to entities retrieved through a WODisplayGroup (for applications that access a database).
item is the key to the current item in that list. Keys
.wod file, or they derive from
WOAssociation objects refer to the current component for the initial value of this sequence. They get this object from the cycle’s WOContext object. Often the current component is the request or response page of the cycle, but it can be a reusable component embedded in a page, or even a component incorporated by one of those subcomponents. See “Subcomponents and Component References” (page 86) for more on this. WOContext stores the current component on a stack, “pushing” and “popping” components onto and off of the stack as necessary.
Depending on the phase of the request-response loop, a dynamic element uses its WOAssociations to “pull” values from the request (that is, set its values to what the user specifies) or to “push” its values onto the response page. When a dynamic element that can respond to user actions (such as WOSubmitButton) requests the value of its “action” WOAssociation, the appropriate action method in the current component is invoked and the response page is returned.
The exchange of data through an association that binds an attribute of a parent component to an attribute of a child component is two-way. This two-way binding allows the synchronization of state between the two components. Consider this declaration in
84
Main.wod of the TimeOff example:
How HTML Pages Are Generated
START:Calendar {
selectedDate = startDate; callBack = "mainPage";
};
In this example, Main is the parent component and Calendar is the child component. The
selectedDate is a variable of the child component. A change in the parent
startDate variable belongs to the parent component while
component instance variable is automatically communicated through the association to the child variable. Conversely, a change in value in the child component variable is communicated to the parent variable. Component synchronization occurs at the beginning and end of each of the three request-handling phases of a component (
invokeActionForRequest:inContext:, and appendToResponse:inContext:). Synchronization is
takeValuesFromRequest:inContext:,
performed through the accessor methods of both components.
This aspect of synchronization has implications for developers. Because WebObjects invokes explicitly implemented accessor methods many times during the same request-response loop, your accessor methods must have no side effects. Instead, they should simply set a variable’s value or return a value. And if they return a value, there should be some way for WebObjects to set the value.
This rule applies also when the binding involves a parent or a child component’s method instead of an instance variable. To illustrate this, assume that
startDate is a method of the Main component instead of an
instance variable. Even in this case, WebObjects attempts to synchronize
startDate with the selectedDate value. In other words, WebObjects attempts to
invoke a
setStartDate: method and raises an exception if such a method does
not exist.
See the chapter “Creating Reusable Components” (page 91) for more on state synchronization between child and parent components.
Associations and Client-Side Java Components
Client-side Java components, like server-side dynamic elements, use associations to synchronize state with the parent component. However, the association class they use is not the same. Instead of using (the WOAssociation equivalent in Java), they use
next.wo.client.Association, and
this Association class is downloaded to the client along with the component itself.
Keys for a client-side component fall into two groups: state bindings and action bindings. State bindings form the basis for state synchronization by associating state in the applets with state in the server. Action bindings
next.wo.Association
85
Chapter 5 WebObjects Viewed Through Its Classes
associate particular events in the client applet (such as clicking a button) with the invocation of methods in the server.
State is synchronized between the client and the server in three phases:
1. When a page is first generated, the server sends the client all state for which there are bindings.
2. Before an action is invoked in the server, the client sends the server any of its state that has changed.
3. After the action is completed, the server sends the client any of its state that has changed.
This last synchronization occurs only if no new page is returned to the browser. When a method invoked remotely through an applet action binding returns it signals that, instead of returning a new page, the server should resynchronize its state with the applets on the page. WebObjects takes a snapshot of the changes in state in the server so that only the state that has changed is sent back to the client.
Note: The last two phases of the synchronization cycle can be initiated only on the
browser side. That is, except for the first “initialization” phase, the server component can react only to an action triggered in an applet. The component cannot unilaterally update the state of an applet when its own state changes.
null,
Subcomponents and Component References
A “node” in a template’s object graph can represent a subcomponent (also called a reusable component) as well as a dynamic or static HTML element. A dynamic element called a component reference represents all occurrences of the subcomponent in the parent component. At runtime, the component reference binds itself to the separate instances. Figure 26 is an example of an object graph for a page with a subcomponent.
A subcomponent can fire actions against its parent component (using
performParentAction:), and if the parent’s state changes, its state is synchronized
accordingly. In other words, its state is updated to reflect changes according to its bindings with the parent.
An element ID is assigned to each instance of a subcomponent. When the chain of request-handling messages traverses an object graph and reaches the component reference, it resolves references to its instances according to the element ID of each instance. Components keep track of all their subcomponents by storing them in an internal dictionary using element IDs as keys.
86
How HTML Pages Are Generated
page template
page component
calendar component reference
calendar instance
= page component
= subcomponent
= static element
= dynamic element
Figure 26. An Object Graph for a Page With a Subcomponent
calendar template
calendar instance
87
SPECIAL TASKS
Chapter 6
Creating Reusable Components
In the simplest applications, each component corresponds to an HTML page, and no two applications share components. However, one of the strengths of the WebObjects architecture is its support of reusable components: components that, once defined, can be used within multiple applications, multiple pages of the same application, or even multiple sections of the same page.
This chapter describes reusable components and shows you how to take advantage of them in your applications. It begins by illustrating the benefits of reusable components. It then describes how to design components for reuse, how reusable components can communicate with the parent component, and how state is synchronized between parent and child components. Finally, it provides some design tips for you to consider when designing your own reusable components.
Benefits of Reusable Components
Reusable components benefit you in two fundamental ways:
They help you centralize application resources.
They simplify interfaces to packages of complex, possibly parameterized, logic and display.
The following sections explain these concepts in detail.
Centralizing Application Resources
One of the challenges of maintaining a web-based application is the sheer number of pages that must be created and maintained. Even a modest application can contain scores of HTML pages. Although some pages must be crafted individually for each application, many (for example, a page that gathers customer information) could be identical across applications. Even pages that aren’t identical across applications can share at least some portions (header, footer, navigation bars, and so on) with pages in other applications. With reusable components, you can factor out a portion of a page (or a complete page) that’s used throughout one or more applications, define it once, and then use it wherever you want, simply by referring to it by name. This is a simple but powerful concept, as the following example illustrates.
Suppose you want to display a navigational control like the one shown in Figure 27 at the bottom of each page of your application.
93
Chapter 6 Creating Reusable Components
Figure 27. A Navigational Control
The HTML code for one page might look like this:
<HTML> <HEAD>
<TITLE>World Wide Web Wisdom, Inc.</TITLE>
</HEAD>
<BODY> Please come visit us again!
<!-- start of navigation control --> <CENTER> <TABLE BORDER = 7 CELLPADDING = 0 CELLSPACING = 5>
<TR ALIGN = center>
<TH COLSPAN = 4> World Wide Web Wisdom, Inc.</TH>
</TR> <TR ALIGN = center>
<TD><A HREF = "http://www.wwww.com/home.html"> Home </a></TD> <TD><A HREF = "http://www.wwww.com/sales.html"> Sales </a></TD> <TD><A HREF = "http://www.wwww.com/service.html"> Service </a></TD> <TD><A HREF = "http://www.wwww.com/search.html"> Search </a></TD>
</TR> </TABLE> </CENTER> <!-- end of navigation control -->
</BODY> </HTML>
Thirteen lines of HTML code define the HTML table that constitutes the navigational control. You could copy these lines into each of the application’s pages or use a graphical HTML editor to assemble the table wherever you need one. But as application size increases, these approaches becomes less practical. And obviously, when a decision is made to replace the navigational table with an active image, you must update this code in each page. Duplicating HTML code across pages is a recipe for irritation and long hours of tedium.
With a reusable component, you could define the same page like this:
94
Benefits of Reusable Components
<HTML> <HEAD>
<TITLE>World Wide Web Wisdom, Inc.</TITLE>
</HEAD>
<BODY> Please come visit us again!
<!-- start of navigation control --> <WEBOBJECT NAME="NAVCONTROL"></WEBOBJECT> <!-- end of navigation control -->
</BODY> </HTML>
The thirteen lines are reduced to one, which positions the WebObject
named NAVCONTROL. The declarations file for this page binds the
WebObject named NAVCONTROL to the component named
NavigationControl:
NAVCONTROL: NavigationControl {};
All of the application’s pages would have entries identical to these in their
template and declarations files.
NavigationControl is a component that’s defined once, for the use of all of
the application’s pages. Its definition is found in the directory
NavigationControl.wo in the file NavigationControl.html and contains the HTML for the
table:
<CENTER> <TABLE BORDER = 7 CELLPADDING = 0 CELLSPACING = 5> <TR ALIGN = center>
<TH COLSPAN = 4> World Wide Web Wisdom, Inc.</TH> </TR> <TR ALIGN = center>
<TD><A HREF = "http://www.wwww.com/home.html"> Home </a></TD>
<TD><A HREF = "http://www.wwww.com/sales.html"> Sales </a></TD>
<TD><A HREF = "http://www.wwww.com/service.html"> Service </a></TD>
<TD><A HREF = "http://www.wwww.com/search.html"> Search </a></TD> </TR> </TABLE> </CENTER>
Since NavigationControl defines a group of static elements, no declaration or code file is needed. However, a reusable component could just as well be associated with complex, dynamically determined behavior, as defined in an associated code file.
Now, to change the navigational control on all of the pages in this application, you simply change the NavigationControl component. What’s more, since reusable components can be shared by multiple applications,
95
Chapter 6 Creating Reusable Components
the World Wide Web Wisdom company could change the look of the navigational controls in all of its applications by changing this one component.
If your application’s pages are highly structured, reusable components could be the prevailing feature of each page:
<HTML> <HEAD>
<TITLE>World Wide Web Wisdom, Inc.</TITLE>
</HEAD>
<BODY>
<WEBOBJECT NAME="HEADER"></WEBOBJECT> <WEBOBJECT NAME="PRODUCTDESCRIPTION"></WEBOBJECT> <WEBOBJECT NAME="NAVCONTROL"></WEBOBJECT> <WEBOBJECT NAME="FOOTER"></WEBOBJECT>
</BODY> </HTML>
The corresponding declarations file might look like this:
HEADER: CorporateHeader {}; PRODUCTDESCRIPTION: ProductTable {productCode = "WWWW0314"}; NAVCONTROL: NavigationControl {}; FOOTER: Footer {type = "catalogFooter"};
Notice that some of these components above take arguments—that is, they are parameterized. For example, the ProductTable component’s is set to a particular product identifier, presumably to display a description of that particular product. The combination of reusability and customizability is particularly powerful, as you’ll see in the next section.
productCode attribute
Simplifying Interfaces
Another benefit of reusable components is that they let you work at a higher level of abstraction than would be possible by working directly with HTML code or with WebObjects’ dynamic elements. You (or someone else) can create a component that encapsulates a solution to a possibly complicated programming problem, and then reuse that solution again and again without having to be concerned with the details of its implementation. Examples of this kind of component include:
•A menu that posts different actions depending on the user’s choice
•A calendar that lets a user specify start and end dates
•A table view that displays records returned by a database query
To illustrate this feature, consider a simple reusable component, an alert panel like the one shown in Figure 28.
96
Benefits of Reusable Components
Figure 28. An Alert Panel
This panel is similar to the navigation table shown in Figure 27, but as you’ll see, most of the component’s attributes are customizable.
To use this component, you simply declare its position within the HTML page and give it a name:
<HTML> <HEAD>
<TITLE>Alert</TITLE> </HEAD> <BODY>
<WEBOBJECT NAME = "ALERT"></WEBOBJECT>
</BODY> </HTML>
The declarations file specifies the value for each of the panel’s attributes, either by assigning a constant value or by binding the attributes value to a variable in the component’s code (as with the
alertString and infoString attributes
here):
ALERT: AlertPanel {
alertString = alertTitle; alertFontColor = "#A00000"; alertFontSize = 6; infoString = alertDescription; infoFontSize = 4; infoFontColor = "#500000"; tableWidth = "50%";
};
The component’s code defines the alertTitle and alertDescription instance variables or methods, which set the text that’s displayed in the upper and lower panes of the alert panel. The
alertDescription method could, for example,
consult a database to determine the release date of the video.
WebObjects Builder makes working with reusable components such as AlertPanel even easier. Component clients can simply drag the alert panel into their components and use the Inspector to set the bindings. They don’t need to manually edit the declarations file to set these bindings. To set this up, you, as the component creator, edit a file named
AlertPanel.api specifying
both required and optional attributes. You could, for example, export only
97
Chapter 6 Creating Reusable Components
the alertTitle and infoString attributes (leaving the other attributes private) using this
AlertPanel.api file:
Required = (alertTitle, infoString); Optional = ();
See the WebObjects Tools and Techniques online book for more information.
AlertPanel is one of several components included in a sample application called ReusableComponents. This application demonstrates and documents how to create and use reusable components. If you look at the source code for AlertPanel, you’ll notice that it’s moderately complicated and, in fact, relies on other reusable components for its implementation. However, WebObjects lets you think of the AlertPanel component as a black box. You simply position the component in your HTML template, specify its attributes in the declarations file, and implement any associated dynamic behavior in your code.
Intercomponent Communication
Reusable components can vary widely in scope, from as extensive as an entire HTML page to as limited as a single character or graphic in a page. They can even serve as building blocks for other reusable components. When a reusable component is nested within another component, be it a page or something smaller, the containing component is known as the parent component, and the contained component is known as the child component. This section examines the interaction between parent and child components.
In the AlertPanel example shown in Figure 28, you saw how the parent component, in its declarations file, sets the attributes of the child component:
ALERT: AlertPanel {
alertString = alertTitle; alertFontColor = "#A00000"; alertFontSize = 6; infoString = alertDescription; infoFontSize = 4; infoFontColor = "#500000"; tableWidth = "50%";
};
Each of the AlertPanel component’s attributes is set either statically (to a constant value) or dynamically (by binding the attribute’s value to a variable or method invocation in the parent’s code). Communication from the parent to the child is quite straightforward.
98
Intercomponent Communication
For reusable components to be truly versatile, there must also be a mechanism for the child component to interact with the parent, either by setting the parent’s variables or invoking its methods, or both. This mechanism must be flexible enough that a given child component can be reused by various parent components without having to be modified in any way. WebObjects provides just such a mechanism, as illustrated by the following example.
Consider an AlertPanel component like the one described above, but with the added ability to accept user input and relay that input to a parent component. The panel might look like the one in Figure 29.
Figure 29. An Alert Panel That Allows User Input
As in the earlier example, you use this component by simply declaring its position within the HTML page:
Parent's Template File
<HTML> <HEAD>
<TITLE>Alert</TITLE> </HEAD> <BODY>
<WEBOBJECT NAME = "ALERT"></WEBOBJECT>
</BODY> </HTML>
The corresponding declarations file reveals two new attributes (indicated in bold):
Parent's Declarations File (excerpt)
ALERT: AlertPanel {
infoString = message; infoFontSize = 4; infoFontColor = "#500000"; alertString = "New Release"; alertFontColor = "#A00000"; alertFontSize = 6; tableWidth = "50%";
parentAction = "respondToAlert"; exitStatus = usersChoice;
};
99
Chapter 6 Creating Reusable Components
The parentAction attribute identifies a callback method, one that the child component invokes in the parent when the user clicks the Yes or No link. The
exitStatus attribute identifies a variable that the parent can check to discover which
of the two links was clicked. This attribute passes state information from the child to the parent. A reusable component can have any number of callback and state attributes, and they can have any name you choose.
Now let’s look at the revised child component. The template file for the AlertPanel component has to declare the positions of the added Yes and No hyperlinks. (Only excerpts of the implementation files are shown here.)
Child's Template File (excerpt)
<TD>
<WEBOBJECT name=NOCHOICE></WEBOBJECT> </TD> <TD>
<WEBOBJECT name=YESCHOICE></WEBOBJECT> </TD>
The corresponding declarations file binds these declarations to scripted methods:
Child's Declarations File (excerpt)
NOCHOICE: WOHyperlink {
action = rejectChoice; string = "No";
};
100
YESCHOICE: WOHyperlink {
action = acceptChoice; string = "Yes";
};
And the script file contains the implementations of the rejectChoice and acceptChoice methods:
Child's Script File (excerpt)
id exitStatus; id parentAction;
- rejectChoice {
exitStatus = NO;
return [self performParentAction:parentAction]; }
- acceptChoice {
exitStatus = YES;
return [self performParentAction:parentAction]; }
Note that exitStatus and parentAction are simply component variables. Depending on the method invoked,
exitStatus can have the values YES or NO. The parentAction
variable stores the name of the method in the parent component that will be
Loading...