DynamicElement is an abstract superclass for classes that generate dynamic elements: objects representing HTML or PDF elements whose values can programmatically change at run time. Dynamic elements have a name and one or more properties, instance variables holding such things as user-entered data or user-triggerable actions. The properties of a dynamic element are associated with, or "bound" to, the properties of the Component object that represents the page (or portion of a page) in which the dynamic element appears.
At runtime, a dynamic element can extract values from the page, feed those values across the bindings to the owning component, receive back new data, and include that data in the next representation of the page. A dynamic element can also detect if the user has manipulated it (for instance, clicking a button) to signal some intention and then trigger the appropriate action method in the owning Component. The bindings between properties of a dynamic element and properties of a Component are made possible by associations, objects that know how to "push" and "pull" values to and from another object using keys. All objects that inherit from NextObject have associative capabilities through NextObject's implementation of the KeyValueCoding interface. (In Objective-C, the mechanism is slightly different; see "Differences Between Java and Objective-C," below.)
DynamicElements must implement the default constructor to initialize their instance variables with the appropriate association objects (passed in). As Element objects, they must also implement one or more of the three request-handling methods. In the context of request handling, a dynamic element can use its associations to:
All dynamic elements must implement appendToResponse. If they accept user input or respond to user actions (such as mouse clicks), they should implement takeValuesFromRequest and invokeAction, respectively.
Dynamic element do not know about their Component object until run time. During request-handling, the application stores components (representing a page and subcomponents on the page) on a stack maintained by the Context object, with he currently referenced Component on top of the stack. A dynamic element's association retrieves the current Component (through an invocation of Context's component method) and reads and writes values from and to the Component using KeyValueCoding methods.
A dynamic element can represent a single HTML or PDF element (such
as an editable text field) or a compound element, such as the
LoginPanel whose implementation is described below. WebObjects
includes a suite of ready-made dynamic elements and the WebObjects
Builder application makes these objects available on its palettes.
The Dynamic Elements Reference describes WebObjects' dynamic elements
and provides examples showing how to use them.
LoginPanel is a dynamic element that consists of a form containing a name field, a password field, and the standard Submit and Reset buttons (renamed Login and Clear). Users type their user name in the first field and a non-echoed password in the second field, and then click the Login (Submit) button. An action method in the associated Component is then invoked, and the Component is responsible for any verification and page navigation.
Figure 1: Example of LoginPanel
Before we get into the nitty-gritty of LoginPanel, here's a quick overview on what happens in the life of a dynamic element. When the application parses the declarations file (".wod") in a component directory, it identifies the name of each declared dynamic element (which is its class name) and the properties of that dynamic element. With this information packaged as arguments, it then invokes the dynamic element's constructor. In the constructor, the dynamic element sets its instance variables to the values of its passed-in properties and invokes its superclass's constructor; the message travels up the inheritance chain until it reaches DynamicElement. (See "Differences Between Java and Objective-C," below, for why this inheritance chain is limited for dynamic elements written in Java.)
During a cycle of the request-response loop, request-handling
messages are propagated down the object graph of elements
representing the request or response page. When it's their turn, all
elements, static and dynamic, respond to appendToResponse by
adding the HTML or PDF code they represent to the response page.
Dynamic elements, depending on their attributes, can also extract
values from the request page (takeValuesFromRequest) and
trigger action methods in the request Component
(invokeAction).
A dynamic element must declare instance variables to hold the association objects that represent its properties. These instance variables must be of type NextObject (in Objective-C, they must be declared as WOAssociation objects). Other (optional) instance variables can contain string constants used as keys for these properties. You should publish these string constants to users of your dynamic element, for they form the variables on the left side of assignments made in the declarations file (or in WebObjects Builder's Inspector panel). For example, LoginPanel's keys are "name,", "password," and "loginAction"; a typical component declaration might therefore be:
Login : LoginPanel { name = theName; password = thePassword; loginAction = verifyUser; }
The ImmutableHashtable (dictionary) that is passed in as an argument contains associations, objects that know how to "push and pull" values to and from a Component object using methods of the KeyValueCoding interface. The associations in this dictionary define initial constant values or bindings between properties of the dynamic element and the Component. Get these associations using the keys you've defined and assign the returned objects to their instance variables.
Code Example:
public class LoginPanel extends DynamicElement { private final String nameKey="name"; private final String passwordKey="password"; private final String actionKey="loginAction"; private NextObject userNameAssociation; private NextObject userPasswordAssociation; private NextObject action; LoginPanel(String aName, ImmutableHashtable associations, Element template) { super(aName, associations, template); try { if (associations.get(nameKey) != null) { userNameAssociation = (NextObject)associations.get(nameKey)); } if (associations.get(passwordKey) != null) { userPasswordAssociation = (NextObject)associations.get(passwordKey); } if (associations.get(actionKey) != null) { action = (NextObject)associations.get(actionKey); } } catch (Exception e){ System.out.println("Error in instantiating LoginPanel, exception = " + e.getMessage()); } }
Implement the appendToResponse method by appending the HTML code that represents the dynamic element to the body of HTML code that already exists for the response page. The Response class has several methods that you can invoke for this purpose.
One technique illustrated in the example code below particularly merits explanation. Several Context methods are invoked to generate element IDs which are used as identifiers for "child" elements. On a particular "node" of the object graph, objects with the same parent have their parent's element ID plus a dot-separated digit appended to it. This digit begins at zero and is incremented according to a "sibling" element's position within the parent. (See the Context class for more on element ID manipulation.)
appendZeroElementIDComponent appends a ".0" to the parent's element ID to create the element ID for the User Name field. The code then assigns the element ID as the HTML name of the input field, "pulls" the required value from the Component property via "userNameAssociation", and assigns this value as the HTML value of the field. Next it increments the last digit of the element ID for the password field and assigns this element ID as the HTML name of the field. When you are finished with the child elements, delete the appended digit, thus restoring the parent's element ID.
Note that no element ID is assigned to the Submit or Request buttons. In HTML, form actions emanate from a form itself and not from the buttons the form contains.
Code Example:
public void appendToResponse(Response aResponse, Context aContext) { String uname; // 1. Generate form aResponse.appendContentString("<form method=\"POST\" action=\""); aResponse.appendContentHTMLAttributeValue(aContext.url()); aResponse.appendContentCharacter((byte)'"'); aResponse.appendContentString(">"); // 2. Generate input fields aResponse.appendContentHTMLString("User Name: "); aResponse.appendContentString("<input type=\"text\" size=10 name=\""); // 3. set HTML name of first field to new elementID aContext.appendZeroElementIDComponent(); aResponse.appendContentHTMLAttributeValue(aContext.elementID()); aResponse.appendContentString("\""); // 4. get user name, make it HTML value uname = userNameAssociation.valueForKey("value").toString(); if (uname != null) { aResponse.appendContentString(" value=\""); aResponse.appendContentHTMLAttributeValue(uname); aResponse.appendContentCharacter((byte)'"'); } aResponse.appendContentString(">"); aResponse.appendContentString("<br>"); aResponse.appendContentHTMLString("Password: "); aResponse.appendContentString("<input type=\"password\" size=10 name=\""); // 5. set HTML name of second field to incremented elementID aContext.incrementLastElementIDComponent(); aResponse.appendContentHTMLAttributeValue(aContext.elementID()); aResponse.appendContentString("\"><br><br>"); aContext.deleteLastElementIDComponent(); // 6. Generate buttons aResponse.appendContentString("<input type=\"submit\" value=\"Login\"> "); aResponse.appendContentString("<input type=\"reset\" value=\"Clear\"><br></form> "); }
If a dynamic element is associated with a text field, check box, or similar UI element used for gathering user data or reflecting choices, it should implement the takeValuesFromRequest method. In this method, it should extract the new or changed value and assign it to the appropriate association. Most user data is entered on forms; you can obtain these form values by invoking Request's formValueForKey or formValuesForKey with the element ID as key. Then "push" the value (or values) into the associated Component property (or properties) with KeyValueCoding's takeValueForKey method.
As in appendToResponse, manipulate the element ID (with Request's ...ElementIDComponent methods) before getting form values. Important: The element IDs assigned to elements should exactly mirror the element IDs assigned to those same elements in appendToResponse.
Code Example:
public void takeValuesFromRequest(Request aRequest, Context aContext) { String uname; String pswd; aContext.appendZeroElementIDComponent(); uname = aRequest.formValueForKey(aContext.elementID()); aContext.incrementLastElementIDComponent(); pswd = aRequest.formValueForKey(aContext.elementID()); aContext.deleteLastElementIDComponent(); userNameAssociation.takeValueForKey(uname,"value"); userPasswordAssociation.takeValueForKey(pswd,"value"); }
If your dynamic element represents an HTML or PDF control--something capable of triggering an action--then it should implement the invokeAction method. When the client generates a request, it embeds in the URL the sender ID , which is the HTML name of the element originating the request. (This name is the element ID you assigned in appendToResponse.) In this method, the dynamic element determines whether it was the activated control by comparing its element ID with the sender ID. You can obtain the sender ID from the Request object passed in as the first argument.
Once a dynamic element establishes that it was the activated control, it invokes the action method in the Component that is bound to the "action" property of the dynamic element. Invocation takes place by getting the value of the "action" property. The action method typically returns the response Component (typed as Element), which invokeAction then returns to its invoker.
As is the case with LoginPanel, a form is responsible for triggering an action, not the form's child elements. Since the element ID assigned to LoginPanel by its parent element is the same as the form's, there is no need to manipulate the element ID before invoking the action method.
Code Example:
public Element invokeAction(Request aRequest, Context aContext) { if (aContext.elementID()).equals(aRequest.senderID()) { if (action != null) { return (Element)action.valueForKey("value"); } else { System.out.println("Couldn't trigger LoginPanel action."); return null; } } else { return super.invokeAction(aRequest, aContext); } }
Because of language constraints and differences in package and framework implementation, how you implement custom dynamic elements in Java differs significantly from how it is done in Objective-C. Java requires the superclass invocation to occur as the first statement in a constructor (which is pointless in this case). In Objective-C, invocation of super in the initializer initWithName:associations:template: takes place after all objects in the associations dictionary that belong to the dynamic element are removed. The implication of this for custom dynamic elements written in Java is that all concrete dynamic element classes must directly inherit from DynamicElement. In other words, these classes are final.
Returns a dynamic element identified by class name and initialized with the objects in dictionary associations. The dictionary contains associations, objects that know how to take values from, and set values in, an "owning" Component. To properly initialize a dynamic element, you should use the published keys of the dynamic element to get the associations that belong to the dynamic element; then assign these objects to instance variables. The template argument, if not null, is the root object of a graph of Elements associated with the dynamic element.
Typically, a key in the associations dictionary is identified with a property of the element, and the value of this key is the name of a property of the associated Component. For example, the value of key "userName" might be bound to "employee.name" in the Component; this designation means that Component has a property called "employee" (possibly referring to an "Employee" object) which in turn has a property called "name". In this case, the binding is two-way; changes in the dynamic element are reflected in the Component property, and changes in the Component property are communicated to the dynamic element. The value of an association can also be a constant, in which case the binding is one-way: Component to dynamic element..