Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Ajax Patterns And Best Practices (2006)

.pdf
Скачиваний:
39
Добавлен:
17.08.2013
Размер:
15.94 Mб
Скачать

 

 

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

179

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 6-13. Chaining Presentation functionalities together

Illustrating a Local Call

Having defined the decoupled library, you can implement a simple example. Even though I have briefly mentioned the Presentation functionality, I haven’t explained the details. Still, although the implementation may seem like we’re jumping a bit ahead of ourselves, I am presenting it on purpose so that you understand the calling sequences.

To illustrate the Decoupled Navigation pattern, I have illustrated the copying of the contents from a text box into an HTML div element. The copied contents will be converted into uppercase. From a GUI perspective, the HTML page looks like Figure 6-14.

Figure 6-14. Example HTML page used to transfer the contents of the text box to the div element, where the contents are converted into uppercase

This HTML page is elementary, and the HTML and JavaScript code behind it are as well. The text box contains the data that is injected into the HTML page and replaces the text Nothing Yet. Following is the code used to create Figure 6-14:

180

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

<html>

<head><title>Processing Local Data</title></head>

<script language="JavaScript" src="/ajax/lib/factory.js"></script> <script language="JavaScript" src="/ajax/lib/asynchronous.js"></script> <script language="JavaScript" src="/ajax/lib/events.js"></script> <script language="JavaScript" type="text/javascript">

var nav = new DecoupledNavigation();

function OnClick( common) {

common.state = new TextState( "divDestination", document.getElementById( "txtContent").value);

return true;

}

function ConvertToUpperCase( common) {

common.state.text = common.state.text.toUpperCase(); return true;

}

</script>

<body>

<div>

<table border="1"> <tr>

<td><input type="text" id="txtContent"></td> </tr>

<tr>

<td>

<input type="button" value="Transfer" onclick="return nav.call ( event, OnClick,

ConvertToUpperCase, InjectHTML)"/>

</td>

</tr>

<tr>

<td id="divDestination">Nothing yet</td> </tr>

</table>

</div>

</body>

</html>

In the HTML code, any HTML element that will be used by the JavaScript code is identified by using the id attribute. This is important so that when manipulations do occur, the JavaScript does not need to hunt for the HTML elements. The JavaScript code declares the variable nav, which is the Decoupled Navigation pattern implementation. The nav variable is used in the onclick event of the input HTML element. The action.local method call wires together the OnClick and ConvertToUpperCase functions with the undefined InjectHTML function. This means that when the button is clicked, the OnClick function is called to process the click,

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

181

the ConvertToUpperCase function is called to convert the case of the text, and the InjectHTML function is called to update the user interface.

Looking closer at the OnClick function, you can see that the class TextState is instantiated. The purpose of TextState is to define a common state structure for a text buffer and an identifier. The TextState structure is passed to and from Action, Data, and Presentation functionalities. The constructor parameters to TextState are the contents of the text box and the destination identifier indicating where the contents are supposed to be injected. The instantiated TextState class is assigned to common.state, which is shared by the still-undefined function InjectHTML.

Consider the overall implementation and that the functions OnClick, ConvertToUpperCase, and InjectHTML are independent of each other. The functions share only the common state structure TextState. For example, to implement functionality whereby the contents of the text box are transferred whenever a letter is added to the text box, the OnClick function needs to be replaced. The OnClick function could be replaced by capturing the onchange event. The remaining functions would remain identical.

Converting the Local Call to a Remote Call

The power of decoupling the three functionalities was quickly explained by replacing the OnClick function. What would be more impressive, though, would be to actually go through an example of changing the processing of the data locally to remotely. The remote server call is a service that converts the local text into bold text. Calling the remote server to convert the text is overkill, but the conversion is meant to illustrate the steps of making a remote server call.

URLs Are Componentized Resources

Making a remote server call means using XMLHttpRequest, and that requires a URL. When calling a URL, it is important that the URL is well designed. When designing URLs, the objective is to design them as if they were components. Treating URLs as components makes it simpler to modularize the functionality.

Some server-side web frameworks—for example, ASP.NET and JavaServer Pages (JSP)— use the first identifier after the slash to identify an application. For example, the URL /application defines the web application application. The idea that the first identifier specifies an application is not a bad idea, and in fact it is a good idea. For example, imagine implementing both the REST-Based Model View Controller and State Navigation patterns. The two patterns require code that executes in the context of an HTTP server. The two patterns are orthogonal in that they offer different forms of functionality. Because they are orthogonal, there is no real reason why they should share variables, state, or code. The subdivision of applications does not need to stop with applications, but can be extended to components, as illustrated in Figure 6-15.

Figure 6-15 shows the root URL /. From the root there are the URLs /search and /state. Each of these URLs represents a resource to a component that implements searching and state navigation functionality. This means that any functionality that starts with /search must relate to search and only search. There cannot be any other type of functionality. Likewise with the URL /state, which means any related URL must relate to implementing state navigation functionality. If some functionality needs to be implemented that does not relate to the URLs, a new component URL is defined.

182

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

 

 

 

 

 

 

 

 

Figure 6-15. Component architecture exposed as URLs

In a nutshell, URLs begin with a general functionality definition and with each appended identifier to the URL the functionality becomes more specific. The URL /search is general, but the URL /search/impl/amazon is specific. The URL /search implements a searching component, whereas /search/impl/amazon relates to a search component specific to Amazon.com. This way of creating URLs is purely resource and state driven, and will conflict with those web applications that map directory structures to URLs for organizational purposes.

Referencing URLs in HTML Pages

When referencing a URL in an HTML page, do you really know what the URL is or should be? It has been explained that URLs should be components, but how are those components discovered? Consider the following link:

<a href="/search/impl/amazon">Amazon Implementation</a>

What does the URL /search/impl/amazon represent philosophically? How do you know that the Amazon.com implementation is at the URL /search/impl/amazon? Even more direct, how do you manage to download the content that references the URL in the first place? There is a German saying, “You can’t smell it,” which means that something needs to be defined somewhere because a URL does not have an odor to guide you to the proper location.

One way to define the URL is to use a JSP or ASP.NET page that generates the URL as follows:

<a href="<%=obj.getAmazonSearchURL()%>"> Amazon Implementation</a>

In the generated code, a method call will generate the URL dynamically based on some logic contained within the method call. The logic could be the retrieval of the URL from a configuration file, or database, or some other persistent mechanism. This approach works well from a traditional application perspective; however, it is completely wrong. If you think abstractly about a URL, you know that a URL is nothing more than an indicator of functionality that you want to invoke. This means that a URL is your abstract resource, and adding an abstraction on an abstraction is wrong.

Therefore, in the original example of hard-coding the URL /search/impl/amazon, it is okay that it is hard-coded because the URL is an abstract resource. Many web application developers do not like to do this because it hard-codes an application. The problem is not the URL, but the

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

183

framework. Imagine a developer using JSP. Then the URL /search/impl/amazon would be rewritten to the following:

<a href="/search/impl/amazon.jsp">Amazon Implementation</a>

The URL ends with the extension .jsp, indicating that amazon.jsp is a JavaServer page. And having the .jsp extension forces a JSP processor. Granted, it is possible to associate the

.jsp extension with an ASP.NET page, or even PHP, but the fact is that the extension is a complication. The Permutations pattern explicitly dictates that externally exposed URLs are treated as components and require the use of a resource that does not tie into a representation like JSP. This does not mean that URLs cannot be dynamically generated. What it means is that

URLs that are exposed as resources as per the Permutations definition will not be dynamically generated. Those URLs will be hard-coded into the HTML page because they represent resources that are already abstracted. And as illustrated per the Permutations pattern, those URLs that are specific to the representation technology can be dynamically generated or hard-coded or retrieved from a configuration file. How the URL is inserted into the content depends on the representation technology.

Hard-coding a URL into an HTML page is difficult for a programmer to swallow. Programmers just don’t like to hard-code any pieces of text, which is completely understandable. After all, programmers have been burned often enough. If you are a programmer who likes to be absolutely fail-safe, load the URL from a configuration file. However, it will not save that much work because the URLs are components, and if the URL is updated, many configuration files will need updating. Remember that we are entering an era of content that is retrieved, cached, archived, referenced, and stored. This means URLs should not change in the first place because changing them will cause problems with other HTTP-based applications. Therefore, think three times before creating the appropriate resource URL.

The one last bit that needs discussion is the referencing of the server. All of the URLs did not have an HTTP server reference. For example, to reference the Apress website, the HTTP server www.apress.com is used. How somebody knows www.apress.com is purely naming convention, as the URL some.cool.apress.server could have been used as well. The URL some.cool. apress.server is not very intuitive, as we have been trained to use www and .com or the appropriate country extension. Another way to discover a URL is through a search engine such as Google. Continuing on this thread, a single server is not appropriate for most HTTP servers; thus a web server farm is necessary.

The complications of figuring out the name of the server are extensive. The Decoupled Navigation pattern offers no solution to the HTTP server reference problem because it is a Domain Name Service (DNS), search engine, and load balancing problem. Today there are already very good implementations of each of these technologies, and writing a pattern about these technologies is futile because most people treat the technologies as black boxes that just happen to work all the time. Yes, I am hand waving and passing the buck, but talking about these technologies is like explaining the philosophy of the perfect garbage collector. We just assume that the garbage collector does their thing properly. Were this book about Internet infrastructure patterns, my answer would be completely different.

Restructuring the Application

Knowing what there is to know about URLs and the local application, the local processing application is converted to call a remote server that will convert the text into bold text.

184

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

Calling the remote server means using an asynchronous callback that makes a request and waits for a response. The HTML code remains almost identical, with a change in the

ConvertToBolded function:

<html>

<head><title>Processing Local Data</title></head>

<script language="JavaScript" src="/ajax/lib/factory.js"></script> <script language="JavaScript" src="/ajax/lib/asynchronous.js"></script> <script language="JavaScript" src="/ajax/lib/events.js"></script> <script language="JavaScript" type="text/javascript">

var nav = new DecoupledNavigation();

function OnClick( common) {

common.state = new TextState( "divDestination", document.getElementById( "txtContent").value);

return true;

}

function ConvertToBolded( common) { common.parent.initializeRemote( common);

common.complete = function( cmdEmbedded, status, statusText, responseText, responseXML) {

cmdEmbedded.state.text = responseText; return true;

}

var buffer = common.state.text; common.async.post( "/ajax/chap10/remotecontent",

"application/text", buffer.length, buffer); return true;

}

</script>

<body>

<div>

<table border="1"> <tr>

<td><input type="text" id="txtContent"></td> </tr>

<tr>

<td>

<input type="button" value="Transfer" onclick="return nav.call ( event, OnClick,

ConvertToBolded, InjectHTML)"/>

</td>

</tr>

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

185

<tr>

<td id="divDestination">Nothing yet</td> </tr>

</table>

</div>

</body>

</html>

The changed content in the HTML page is bold. The changes are to only one function. This means that the change from processing data locally to remotely has been implemented transparently without updating the HTML elements responsible for the user interface, the OnClick or the InjectHTML function. The overall application still looks and feels the same, with the only noticeable change being the speed of converting the text to bold.

Let’s focus on ConvertToBolded, which is illustrated again as follows:

function ConvertToBolded( common) { common.parent.initializeRemote( common);

common.complete = function( cmdEmbedded, status, statusText, responseText, responseXML) {

cmdEmbedded.state.text = responseText; return true;

}

var buffer = common.state.text;

common.async.post( "/ajax/chap10/remotecontent.html", "application/text", buffer.length, buffer);

return true;

}

In the implementation of the ConvertToBolded, there is a call to initializeRemote. The method initializeRemote sets up the functions and data members necessary to make a remote server call by using the Asynchronous type. The definition of the common.complete function is required by Asynchronous and is called when the remote call has completed. The existence of common.complete splits the Common Data functionality into two pieces. The first piece is the creation of the remote server call request, and the second piece is the processing of the results.

The last part of the ConvertToBolded method is to send the data to the server by using the method common.async.post (HTTP POST). Sending the data is the first step of the two-step Common Data functionality. The server will process the data and return a modified state to the caller. The modified state is then processed by the common.complete method, which is the second step of the two-step Common Data functionality. As the second step is part of the Common Data functionality, the Presentation functionality can be called thereafter.

Before the implementation of initializeRemote is started, a better way to explain the calling sequence is to illustrate it. Figure 6-16 makes it simpler to explain how the method initializeRemote is implemented.

186

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 6-16. Calling sequence for the method nav.call

In Figure 6-16, when the user clicks the Transfer button, the event onclick is triggered. The onclick event calls the method nav.call, which is of the type DecoupledNavigation. DecoupledNavigation has two methods (call and complete) that are of interest when calling a remote server. Executing the call method will call the Action functionality (OnClick) and the Data functionality (ConvertToBolded). The Data functionality will wire up the asynchronous HTTP call, and call the remote server. At this point the Decoupled Navigation pattern gives up control and waits for a response from the server.

When the server receives a response, it is captured by DecoupledNavigation.complete, which then delegates to common.complete. Calling common restarts the Decoupled Navigation pattern and finishes the Data functionality part. Thereafter, the Presentation functionality starts, which calls the function InjectHTML. Calling InjectHTML causes the user interface to change and contains the bold code.

Now that you understand the sequence of events, let’s look at the method initializeRemote, which is responsible for wiring together the various methods:

function DecoupledNavigation_InitializeRemote( common) { common.async = new Asynchronous();

common.complete = function( obj, status, statusText, responseText, responseXML) {}

common.openCallback = function( xmlhttp) {} common.async.openCallback = function( xmlhttp) {

common.openCallback( xmlhttp);

};

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

187

common.async.complete = function( status, statusText, responseText, responseXML) {

if ( (common.complete) != null) {

if ( common.complete( common, status, statusText, responseText, responseXML) == true) {

if ( (common.presentation) != null) { common.presentation( common, common.state);

}

}

}

}

common.isRemote = true;

}

The variable common is the object reference to the state that is passed across the various functionalities. The property common.async represents an Asynchronous instance. As explained in Chapter 2, Asynchronous requires an implementation for the property complete because that property is a function reference that will be called by Asynchronous when the server returns a response.

Look closer at the implementation common.async.complete. In the implementation, the user’s complete (common.complete) is called if it exists. The user’s complete is the second step of the Common Data functionality. If the complete function returns true, the common.presentation function reference (if it exists) is called.

Implementing the Presentation Functionality

When implementing the Decoupled Navigation pattern, the Presentation layer is where the output is generated. The examples thus far have been very simple; the output has been an HTML injection. In a more sophisticated Ajax application, the output would be more complicated and would involve the creation of pop-up boxes, as illustrated in Figure 6-17.

Figure 6-17. A more complicated user interface that uses a pop-up box

188

C H A P T E R 6 D E C O U P L E D N A V I G A T I O N P A T T E R N

Figure 6-17 shows an HTML page containing a box that can be dragged around the page. To make the box work, and be “draggable,” quite a bit of Dynamic HTML magic is going on. Here is the important fact: the HTML box is Dynamic HTML, and not Ajax per se. As per the original definition,2 Ajax is not a technology, but the combination of several already-existing technologies.

The focus of the Ajax patterns is not to explain the Dynamic HTML components, but to use the components in the context of Ajax. Part of the reason why this book does not attempt to explain and illustrate the Dynamic HTML components is that plenty of scripts do those tasks very well.3 The focus of this book is to integrate those already-existing components and make them Ajax aware. The aim of the Presentation functionality is to use the Dynamic HTML components to support the navigation.

What is interesting about Figure 6-17 is that the Dynamic HTML to make the draggable pop-up box work is fairly complicated, but the content within the box is rather simple. It shows that the author of the component took great effort to make it easy to use the draggable pop-up box. Consider the following abbreviated source code that creates the pop-up box:

<body>

<div id="showimage" style="position:absolute;width:250px;left:250px;top:250px"> <table border="0" width="250" bgcolor="#000080" cellspacing="0" cellpadding="2">

<tr>

<td width="100%">

<table border="0" width="100%" cellspacing="0" cellpadding="0" height="36px">

<tr>

<td id="dragbar" style="cursor:hand; cursor:pointer" width="100%" onMousedown="initializedrag(event)"> <ilayer width="100%" onSelectStart="return false"> <layer width="100%"

onMouseover="dragswitch=1;if (ns4) drag_dropns(showimage)" onMouseout="dragswitch=0">

<font face="Verdana" color="#FFFFFF">

<strong><small>Announcement Box</small></strong></font> </layer>

</ilayer>

</td>

<td style="cursor:hand">

<a href="#" onClick="hidebox();return false"> <img src="close.gif" width="16px"

height="14px" border="0"></a></td>

</tr>

2.http://www.adaptivepath.com/publications/essays/archives/000385.php

3.http://www.dynamicdrive.com, http://www.dhtmlcentral.com, http://scriptasylum.com, http:// www.hotscripts.com, http://www.howtocreate.co.uk, http://webdeveloper.earthweb.com, and so on. If I did not mention your website, I am sorry. Send me an e-mail at christianhgross@gmail.com, and I will create a reference list at the URL http://www.devspace.com:8080.