GetState SetState
Result representation
GetState SetState
|
lTttp;/yjGcalfr(>5t;BQ68 - Prsrceissliig Remote - Mo: _ □ X | |
|
- Number 1 ¡~~ | |
|
Nuittber 2 | |
|
Add the two numbers | | |
|
1 Pane | |
Figure 3-3. Applying the Representation Morphing pattern when adding two numbers
Figure 3-3 shows two referenced representations: Number and Result. Each of the representations has associated two methods: GetState and SetState. The purpose of the methods is to extract and assign state to a representation. In programmatic terms, the representations and the associated methods represent a contract. Notice that it has not been mentioned how the data is retrieved from the server. This is important, because it illustrates that the representation is not dependent on how it receives its data.
The architecture cannot be directly implemented as illustrated in Figure 3-3, because HTML considers everything as a single namespace. Thus, if one script defines a function, another script cannot define the same function. In the architecture of Figure 3-3, there are two instances of GetState and SetState. The Representation Morphing pattern solves the problem by associating the state methods with the HTML blocks. In the code example, that means associating state methods to the form and to the span element.
Another solution is to create the global functions GetState and SetState but associate identifiers with the global functions. These global functions are responsible for retrieving or assigning the state with the representation. The advantage of this approach is that you have a centralized location that you can extend or maintain.
Going back to the first code segment of this recipe, adding the global functions GetState and SetState results in the following code.
Source:/website/ROOT/ajaxrecipes/dhtml/validation/test.html
<title>Validation Example</title>
<script language="JavaScript" src="/scripts/jaxson/common.js"x/script> <script language="JavaScript" src="/scripts/jaxson/converter.js"></script> </head>
<script language="JavaScript" type="text/javascript">
function GetState(identifier, cb) { }
function SetState(identifier, obj, cb) { }
<body> form id="calculator"> <table border="1"> <tr>
<td>Number 1</td><td><input type="text" name="Number1" /></td> </tr> <tr>
<td>Number 2</td><td><input type="text" name="Number2" /></td> </tr> <tr>
<td>Result</td><td><span id="result"></span></td> </tr> <tr>
<input type="button" value="Add the two numbers" onclick=""/> </center> </td> </tr> </table> </form> </body></html>
The modified code, shown in bold in the updated HTML page, illustrates the definition of the functions. The functions GetState and SetState provide the base infrastructure used to retrieve and assign state to a representation. This means that GetState and SetState don't include functionality to make Ajax requests.
The function GetState has two parameters: identifier and cb. The identifier parameter is the identifier of the representation to extract the state from. Figure 3-3 contains two representations, which results in the parameter identifier having two values. The second parameter cb is a callback function that is called to indicate an error or retrieved state. The second parameter is a code block, as defined in Chapter 2. The reason for using a code block relates to the multifunctionality of the GetState or SetState function. More about this will be discussed in a moment.
SetState has a similar declaration to GetState, except that it includes an additional parameter obj. The parameter obj is an object that is a state that will be assigned to the representation. It is assumed that the representation knows about the data members defined by the variable obj.
In the Representation Morphing pattern, the GetState and SetState functions are used to assign and extract a state from a representation. The Representation Morphing pattern focuses on the ability to serialize a representation, decouple a state from the representation, and serialize the state when the representation is serialized. For this recipe, there is the additional requirement to perform validation. From a programmatic perspective, adding validation is straightforward. What makes validation more complicated is the additional interaction aspects. For example, what would the application do if a validation failed? Should it display a dialog box or a blinking message? If a validation error occurs, should the application show the first error and stop validating?
Let's focus on the extraction of the state using the function GetState. You must implement the following details when using GetState:
• When extracting the state for the numbers, you must reference the text box value properties for Number1 and Number2.
• When extracting the state for the result, you must reference the span innerHTML property.
• You must validate the text box values Number1 and Number2 as being numbers.
• If the validation fails, you must generate and display an error so users can take corrective action.
• You must not display errors on a piecemeal basis. The validation routines must go through the entire state and generate errors for everything found.
• When no more validation errors occur, pass the generated state to the caller. Note that multiple state instances might occur.
You should notice the generation of multiple errors or multiple state instances. Whenever an algorithm is confronted with multiple results, the results are stored temporarily in an array that is processed by the caller of the algorithm. As shown in Chapter 2's code block recipe, you could also use code blocks. Code blocks would be the appropriate choice, because there may or may not be errors, and there may or may not be a state instance.
To illustrate the complexities, consider the following code, which illustrates each approach of calling the GetState function:
function GenerateState() {
var noErrors = true;
var result = GetState( "identifier");
for( int c1 = 0; c1 < result.errors.length; c1 ++) { // Do something with error noErrors = false;
for( int c1 = 0; c1 < results.state.length; c1 ++) { // Do something with the results
The first line is an assignment of the variable noErrors. The variable noErrors is used to indicate whether errors occur when extracting the state. If there are errors, then processing the state would be silly since there is no state—or if there is, it is incomplete.
Calling the function GetState returns an object instance, which has two data members: errors and state. The two data members are arrays that contain the errors and generated state instances. After the call to GetState, a loop iterates the validation errors and, if necessary, generates a response. If an error is generated, the variable noErrors is assigned a value of false, indicating an error. If there are no errors, the generated state instances are iterated.
As GetState is coded, the function is called and processes the state. If an error occurs, then the caller of GetState needs to dissect what went wrong and how to indicate the errors to the caller. Another solution is to use code blocks that simplify how the state or errors are processed. Code blocks simplify the code, because they allow you to focus on adding value with respect to code. The following code illustrates how a version of GetState uses code blocks:
function GenerateState() {
GetState( "Identifier", {
error : function( errorItem) { // Do something with error
state : function( stateInstance) { // Do something with results
In the modified implementation of GenerateState, the GetState function is passed an object instance that has two methods: error and state. Whenever an error occurs, the function error is called. If no errors occur, then the function state is called. The caller of GenerateState has a simplified implementation, because it only needs to take care of the cases when an error or a state instance occur. If the caller doesn't provide an implementation for the function error, then any errors that would occur are ignored, and the caller only waits for a valid state instance. The details of SetState and how to use it are similar to GetState. The difference with SetState is that a state is being assigned to a representation.
Now let's look at the implementation details of the HTML form again, as one additional change has not yet been discussed, and it needs to be covered before discussing the details of GetState or SetState:
form id="calculator"> <table border="1"> <tr>
<td>Number 1</td><td><input type="text" name= <br><span id="NumberlError"></span></td>
<td>Number 2</td><td><input type="text" name= <br><span id="Number2Error"></span></td>
<td>Result</td><td><span id="result"></span> <br><span id="resulterror"></span></td>
<input type="button" value="Add the two </center> </td> </tr> </table> </form>
The bold text shows the addition of HTML span elements, which you use to display any errors associated with the data. In past applications, you might have used a dialog box to indicate errors. The problem with dialog boxes is that they talk about the problematic data but don't pinpoint it. With a fairly complex form, users might be left wondering where the error is. Dynamic HTML (DHTML) gives you the ability to modify the HTML elements, thus making the need to use a dialog box unnecessary.
This recipe uses an HTML span element that contains the error. You can use whatever you want in your applications. Maybe you want to use blinking text, or maybe you want to change fonts—it's your choice. It's important, though, that you associate the error with the local HTML element.
Now let's cover the details of GetState, which can be abit lengthy:
function GetState(identifier, cb) { if (identifier == "toadd") { // 1
var form = document.getElementById("calculator"); document.getElementById("NumberlError").innerHTML = ""; // 2 document.getElementById("Number2Error").innerHTML = ""; var obj = new Object(); // 3 var didError = false; // 4
numbers" onclick=""/>
obj.Number1 = Converter.convertToInteger(form.Number1.value); // 5
didError = true;
document.getElementById("Number1Error").innerHTML = e.toString(); if (cb.error) {
cb.error({ section : "toadd", item: "Number1", error : e.toString()}); // 6
obj.Number2 = Converter.convertToInteger(form.Number2.value);
didError = true;
document.getElementById("Number2Error").innerHTML = e.toString(); if (cb.error) {
cb.error({section: "toadd", item: "Number2", error: e.toString()});
cb.state({ section: "toadd", value : obj}); // 7
var element = Navigation.findChild("calculator", "result"); Navigation.findChild("calculator", "resulterror").innerHTML = ""; var obj = new Object();
obj.Result = Converter.convertToInteger(element.innerHTML);
Navigation.findChild("calculator", "resulterror").innerHTML =
cb.error({section: "Result", item : "Result", error: e.toString()});
return;
cb.state({ section : "result", value : obj});
cb.error({section: identifier, error: "State identifier (" + identifier + "does not exist"});
The code is not complicated but lengthy, because the task you need to accomplish is lengthy. The following list explains each of the bold lines of code. The numbers in the list correspond to the numbers in the comments displayed in the highlighted code:
1. GetState makes a decision to determine which representation should be converted into a state.
2. GetState resets the error messages associated with the representation. In this recipe, that means assigning the innerHTML property of the individual HTML span elements to an empty buffer. In your application, that might mean the resetting the blinking text or changing the text font. It's important to reset the error state so that no old errors are displayed as the validation is being executed.
3. GetState instantiates an object using the Object type. The instantiation and use of the Object type has a purpose. You might be tempted to instantiate a type that has predefined data members and methods, but that is not advised. Imagine using DHTML and generating a form dynamically. It could be that one context of the generated form has a data member, and another context does not. From a state perspective, you want to know the state that reflects the context, not what you think the context should be. Thus, when you instantiate an Object that has no data members and you assign the data members dynamically, you're ensuring only that the data associated with the context is present.
4. The variable didError is a flag that indicates whether a validation error occurred. This flag is highlighted here to cross-reference the previous discussion regarding the reason of using callbacks and not loops. The illustration of didError shows that the GetState algorithm needs to track whether an error occurred.
5. Converter converts the HTML data into the requested type, which in the case of the example is an integer value. The conversion is a function call that varies with the application being written. The conversion includes a validation. Note that the conversion is encapsulated within an exception block. The use of an exception block is preferred, because all errors will be caught. The validation routines might miss some errors, but the exception block can capture and display those errors.
6. If an exception is generated, the catch block captures the exception. Once the exception has been captured, the user-defined error callback is called and can process the error further.
7. If no errors are generated, the user-defined callback is called with the state of the form.
The code that is not bold is either a replication of the functionality or support code for one of the seven details. Remember that the GetState and SetState functionalities are self-contained. For example, when a validation fails and an error is generated, you have the option of making the callback display the message. However, this approach isn't desirable, because the state code would have to know about the representation details. As per the Representation Morphing pattern, it is not desirable to have the caller of GetState or SetState know how the representations are implemented. This promotes a decoupling, just as the GetState and SetState functions don't know the origin of the data used to assign a state in a representation.
For illustration purposes, the following code displays the complete SetState functionality and cross-references the details that implement the same functionality as the seven defined details of the GetState function:
function SetState(identifier, obj, cb) { if (identifier == "toadd") { // 1
var form = document.getElementById("calculator"); document.getElementById("NumberlError").innerHTML = ""; // 2 document.getElementById("Number2Error").innerHTML = ""; if (typeof(obj.Numberl) != "number") { // 5
var buffer = "obj.Number1 expected a number, but is a " +
typeof(obj.Number1); document.getElementById("Number1Error").innerHTML = buffer; if (typeof(cb) != "undefined" && cb.error) {
cb.error({ section: "toadd", identifier : "Numberl", error : buffer}); // 6
var buffer = "obj.Number2 expected a number, but is a " +
typeof(obj.Number2); document.getElementById("Number2Error").innerHTML = buffer; if (typeof(cb) != "undefined" && cb.error) {
cb.error({ section: "toadd", identifier : "Number2", error : buffer});
form.Number1.value = obj.Number1; form.Number2.value = obj.Number2;
var element = Navigation.findChild("calculator", "result"); if (typeof(obj.Result) != "number") {
var buffer = "obj.Result expected a number, but is a " +
typeof(obj.Result); Navigation.findChild("calculator", "resulterror").innerHTML = buffer; if (typeof(cb) != "undefined" && cb.error) {
cb.error({ section: "result", identifier : "Result", error: buffer});
element.innerHTML = obj.Result;
if (typeof(cb) != "undefined" && cb.error) {
cb.error({section: identifier,error: "State identifier (" + identifier + "does not exist"});
When assigning the representation with a state, the same sort of logic is carried out, with the exception of lines 3 and 7 from the previous code listing. Those numbers are not referenced here, because when assigning a state, you're passed the object instance that contains the state.
The server-side validation is not shown, because this chapter focuses on client-side solutions. To complete the solution, you would use an Ajax request and send or receive the state from the server. It is not necessary to send the state to the server, since you could use the state for other purposes, as Figure 3-4 illustrates.
Post a comment