Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax In Action (2006).pdf
Скачиваний:
63
Добавлен:
17.08.2013
Размер:
8.36 Mб
Скачать

Refactoring 453

To test the new functionality, log into the portal as our test user. Drag the windows around the screen, and resize them so they are in different positions from their defaults. Close the browser to force an end to the session. When we reopen the browser and log back into the portal as that user, we see the windows in the same position. Move the windows to a new position and look at the database table. We are automatically saving the user’s preferences without him even knowing it.

We’ve now provided all the basic functionality for a working portal system, including a few things that a classic web application just couldn’t do. There are several other requirements that we could classify as “nice to have,” such as being able to add, remove, and rename windows. Because of limited space, we are not going to discuss them here. The full code for the portal application is available to download and includes the ability to add, delete, rename, and adjust the window’s properties without leaving the single portal page. If you have any questions about the code in this section or need a more thorough understanding, you can always reach us on Manning.com’s Author Online at www.manning.com.

Our code so far has been somewhat rough and ready so that we could demonstrate how the individual pieces work. Let’s hand it over to our refactoring team now, to see how to tighten things up and make the system easier to reuse.

11.6 Refactoring

The concept of an Ajax-based portal client that interacts with a server-side portal “manager” is, as you’ve seen, a compelling notion. In our refactoring of this chapter’s client-side code, let’s consider our component as an entity that serves as the arbitrator of portal commands sent to the portal manager on the server. Throughout this refactoring discussion, let’s make it our goal to isolate the pieces of code that might change over time and facilitate those changes as easily as possible. Since the portal is a much coarser-grained component and something that will more or less take over the real estate of our page, we won’t be so stringent with the requirement of not interrupting the HTML as we have in the previous two refactoring examples.

But, before discussing the client-side semantic, let’s first stop and contemplate the contract with the server. Our previous server-side implementation was written in Java, so we had a servlet filter perform the authentication functionality: one servlet to return the window configurations, and another servlet to save window configurations. Similarly, for adding new windows and deleting the current ones, we would provide further standalone servlets. In a Java web application, the servlets

454CHAPTER 11

The enhanced Ajax web portal

can be mapped to URLs in a very flexible fashion, defined in the web.xml file of the web archive (.war) file. For example, our SelectServlet, which returned the script defining the initial windows, was mapped to the URL portalLogin.servlet.

One of the strengths of Ajax is the loose coupling between the client and the server. Our portal example uses Java as a back-end, but we don’t want to tie it to Java-specific features such as servlet filters and flexible URL rewriting. An alternative back-end architecture might use a request dispatch pattern, in which a single servlet, PHP page, or ASP.NET resource accepts all incoming requests and then reads a GET or POST parameter that specifies what type of action is being undertaken. For example, the URL for logging in to the portal might be portal?action=login&userid=user&password=password or, more likely, the equivalent using POST parameters. In Java, we might implement a request dispatcher approach by assigning a specific URL prefix, say .portal, to the dispatcher servlet, allowing us to write URLs such as login.portal.

In our refactored component, we will generalize our assumptions about the back-end to allow either a request dispatcher architecture or the multiple address option that we used for our Java implementation. We don’t, however, need to introduce complete flexibility, so we’ll predefine a number of commands that the portal back-end will be expected to understand, covering login, showing the user’s portal windows, and adding and deleting windows from the portal. With these changes to the server in mind, let’s return our attention to the client-side implementation.

Let’s begin our discussion of the portal refactoring by redefining the usage contract from the perspective of the page’s HTML; then we’ll delve into the implementation. Recall that the hook from our HTML page into the portal script was via the login, specifically through the login button:

<input type="button" name="btnSub" value="login" onclick="LoginRequest('login')">

We’ll change the onclick handler to be a call to a function that will use our portal component. Let’s assume that the portal component will be instantiated via a script that executes once the page loads. A representative example of what this should look like is shown in listing 11.13.

Listing 11.13 Portal creation and login

function createPortal() { myPortal = new Portal(

'portalManager', b Base URL for portal

{

 

 

Refactoring

455

 

 

 

 

 

 

 

 

messageSpanId: 'spanProcessing',

 

c

Optional

 

 

 

urlSuffix: '.portal'

 

 

parameters

 

}

);

myPortal.loadPage(Portal.LOAD_SETTINGS_ACTION); d Call to load windows document.getElementById('username').focus();

}

function login() {

myPortal.login( document.getElementById('username').value, document.getElementById('password').value );

}

In this usage semantic, createPortal(), which should get called once the page loads, creates an instance of the portal component. The first argument is the base URL for the portal’s server-side application b, and the second provides optional parameters used to customize it for a particular context c. In this case, we tell it the ID of the DOM element into which status messages should be written and the name of the request parameter that will denote which action to execute. Once created, an API on the portal named loadPage is called. This loads the page’s portal windows if there is already a user login present in the server session d. If nobody is logged in, this server will return an empty script, leaving only the login form on the screen.

The login() function is just a utility function in the page that calls the login() method of our portal component, passing the username and password values as arguments. Given this contract, the login button’s onclick handler now calls the page’s login() method, as shown here:

<input type="button" name="btnSub" value="login" onclick="login()">

11.6.1Defining the constructor

Now that you have a basic understanding of how the component will be used from the perspective of the page, let’s implement the logic, starting with the constructor:

function Portal( baseUrl, options ) {

this.baseUrl

=

baseUrl;

this.options

=

options;

this.initDocumentMouseHandler();

The constructor takes the URL of the Ajax portal management on the server as its first argument and an options object for configuration as the second. In our

456CHAPTER 11

The enhanced Ajax web portal

earlier development of the script, recall that we had a servlet filter and two servlets perform the back-end processing. Throughout the rest of this example, we’ll assume a single servlet or resource, portalManager, which intercepts all requests to the portal back-end, as configured in listing 11.13. If we wanted to configure the portal against a back-end that didn’t use a single request dispatcher, we could simply pass different arguments to the constructor, for example:

myPortal = new Portal( 'data',

{ messageSpanId: 'spanProcessing', urlSuffix: '.php' } );

This will pass a base URL of “data” and, because no actionParam is defined in the options array, append the command to the URL path, with the suffix .php, resulting in a URL such as data/login.php. We’ve given ourselves all the flexibility we’ll need here. We’ll see how the options are turned into URLs in section 11.6.3. For now, let’s move on to the next task. The final line of the constructor introduces the issue of adapting the AjaxWindows.js library.

11.6.2Adapting the AjaxWindows.js library

Recall that the implementation of this portal used an external library called AjaxWindows.js for creating the individual portal windows and managing their size and position on the screen. One of the things we had to do was to adapt the library to send Ajax requests to the portal manager for saving the settings on the mouseup event. This was the hook we needed; all move and resize operations are theoretically terminated by a mouseup event. The way we performed the adaptation in round one was to make a copy of the AjaxWindows.js library code and change the piece of code that puts a mouseup handler on the document. If we think of the AjaxWindow.js library as a third-party library, the drawback to this approach is evident. We’ve branched a third-party library codebase, that is, modified the source code and behavior of the library in such a way that it’s no longer compatible with the version maintained by its original author. If the library changes, we have to merge in our changes with every new version we upgrade to. We haven’t done a good job of isolating this change point and making it as painless as possible. Let’s consider a less-radical approach of adaptation and see if we can rectify the situation. Recall the last line of our constructor:

this.initDocumentMouseHandler();

Our initDocumentMouseHandler() method is an on-the-fly adaptation of the AjaxWindows.js library. It just overrides the document.onmouseup as before, but within

Refactoring 457

our own codebase instead. Now our own method will perform the logic required to perform the adaptation within the portal’s handleMouseUp() method. This is shown in listing 11.14.

Listing 11.14 Adaptation of the AjaxWindows.js mouse hander

initDocumentMouseHandler: function() { var oThis = this;

document.onmouseup = function() { oThis.handleMouseUp(); };

},

handleMouseUp: function() {

bDrag

= false;

bResize

=

false;

intLastX =

-1;

document.body.style.cursor = "default";

if ( elemWin && bHasMoved ) this.saveWindowProperties(elemWin.id);

bHasMoved = false;

},

This solution is much better, but we could take it one step further. If the AjaxWindows.js library defined the mouseup handler within a named function rather than anonymous, we could save the handler under a different name and invoke it from our own handler. This would have the benefit of not duplicating the logic already defined in the AjaxWindows.js library. This approach is illustrated in the following code:

function ajaxWindowsMouseUpHandler() { // logic here...

}

document.onmouseup = ajaxWindowsMouseUpHandler;

ajaxWindowsMouseUpHandler() is a callback defined by the AjaxWindows.js external library. Using it would allow us to save the definition of the method and use it later, as shown here:

initDocumentMouseHandler: function() { this.ajaxWindowsMouseUpHandler =

ajaxWindowsMouseUpHandler;

b Store our own reference

var oThis = this;

 

document.onmouseup = function() { oThis.handleMouseUp(); };

},