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

458CHAPTER 11

The enhanced Ajax web portal

handleMouseUp: function() { this.ajaxWindowsMouseUpHandler(); c Call library function if ( elemWin && bHasMoved )

this.saveWindowProperties(elemWin.id);

d Add our functionality

},

 

Now our handleMouseUp() method doesn’t have to duplicate the AjaxWindows.js library functionality. We just invoke the functionality cthrough our saved reference b and then add our own functionality d. And if the mouseup handler of AjaxWindows changes in the future, we pick up the changes without requiring any code modifications. This is a much more palatable change-management situation. Of course, it does assume that the implied contract with the library doesn’t change—the contract being two global variables named elemWin and bHasMoved. Given that the library currently defines the mouseup handler as an anonymous function, we could still save a reference to the existing mouseup functionality with a line of code such as

this.ajaxWindowsMouseUpHandler = this.document.onmouseup;

This would achieve the same thing, but it’s a slightly more brittle proposition, since the contract in this situation is much looser. This solution relies on the fact that we’ve included our script libraries in the appropriate order and that the AjaxWindows.js library has already executed the code that placed the mouseup handler on the document. It also assumes no other library has placed a different mouseup handler on the document or has performed some other wrapping technique just as we’ve done.

That’s probably about as much as we can hope to do with the library adaptation. Let’s move on to the portal API. The handleMouseUp() method reveals one of the three portal commands that the portal component has to accommodate. When the mouse button is released, the saveWindowProperties() method is called to save the size and position of the current window. The following discussion will detail that along with the other portal command APIs.

11.6.3Specifying the portal commands

As already discussed, our portal component is primarily a sender of commands. The commands that are sent are Ajax requests to a server-side portal management system. We’ve already discussed the notion of commands and the formal Command pattern in Ajax, in chapters 3 and 5. Here is another opportunity to put that knowledge to use.

The commands that we’ve supported up to this point in our portal are logging in, loading settings, and saving settings. We’re going to throw in the ability to add

Refactoring 459

and delete windows, which we alluded to although we didn’t show the full implementation. We can think of each of these in terms of a method of our portal. But before we start looking at code, let’s do a bit of prep work to help with the task of isolating change points. What we’re referring to is the names of the commands themselves. Let’s define symbols for each command name so that the rest of our components can use them. Consider the following set of symbols:

Portal.LOGIN_ACTION

= "login";

Portal.LOAD_SETTINGS_ACTION

= "PageLoad";

Portal.SAVE_SETTINGS_ACTION

= "UpdateDragWindow";

Portal.ADD_WINDOW_ACTION

=

"AddWindow";

Portal.DELETE_WINDOW_ACTION

=

"DeleteWindow";

Even though the language doesn’t really support constants, let’s assume that based on the uppercase naming convention, these values are intended to be constant values. We could lazily sprinkle these string literals throughout our code, but that’s a fairly sloppy approach. Using constants in this way keeps our “magic” strings in a single location. If the server contract changes, we can adapt. For example, imagine the ways in which the server contract could change, as shown in table 11.1.

Table 11.1 Public contract changes

Server Contract Change

Action Required

 

 

A command is renamed (e.g., PageLoad gets

Change the right side of the assignment of the

renamed to its verb-noun form LoadPage).

LOAD_SETTINGS_ACTION constant to the new

 

value. The rest of the code remains unaffected.

 

 

The server no longer supports a command.

Remove the constant, and do a global search for all

 

references. Take appropriate action at each refer-

 

ence point.

 

 

The server supports a new command.

Add a constant for the command, and use its name

 

within the code.

 

 

Now that we can reference commands by these symbols, let’s look at a generic mechanism for issuing the commands to the portal management server. We need a helper method that generically sends Ajax-based portal commands to the server. Consider this usage contract:

myPortal.issuePortalCommand( Portal.SAVE_SETTINGS_ACTION, "setting1=" + setting1Value, "setting2=" + setting2Value, ... );

In this scenario, we’re contemplating a method named issuePortalCommand() that takes the name of a command as its first argument (for example, one of our

460CHAPTER 11

The enhanced Ajax web portal

constants) and a variable number of arguments corresponding to the parameters the command expects/requires. The parameters are, quite intentionally, of the exact form as that required by the net.ContentLoader’s sendRequest() method. The issuePortalCommand() method we’ve defined could be implemented as follows:

issuePortalCommand:

function( commandName ) {

 

var

actionParam

=

this.options['actionParam']; b Get action parameter

var

urlSuffix =

this.options['urlSuffix'];

c Get URL suffix

if (!urlSuffix) urlSuffix=""; var url = this.baseUrl;

var callParms = []; if (actionParam){

callParms.push(

actionParam + "=" + commandName d Apply action parameter

);

 

 

 

 

}else{

 

 

Apply URL

 

url += "/" + commandName

 

e

 

 

 

+ urlSuffix;

 

 

suffix

 

}

 

 

 

 

for ( var i = 1 ; i < arguments.length ; i++ )

 

callParms.push( arguments[i] );

 

 

var ajaxHelper = new

 

 

 

f Create ContentLoader

net.ContentLoader( this, url, "POST", [] );

ajaxHelper.sendRequest

 

 

g Send request

.apply( ajaxHelper, callParms );

},

This method builds a URL based on the configuration options that we discussed in section 11.6.1. If we have supplied a value for actionParam b, then it will be added to the parameters that are POSTed to the server d. If not, we will append the command to the URL path e, adding the URL suffix if we have supplied one in our options c. The first function argument is the command name. All remaining arguments are treated as request parameters. The URL that we have constructed is then passed to the ContentLoader f, and the request is sent with the request parameters in tow g, as illustrated in the example usage shown previously. With this method in place, each of our portal command APIs will have a nicely minimal implementation. Another “for free” feature of having a generic method like this is that we can support new commands that become available on the server without having to change any client code. For now, let’s look at the commands we do know about.

Refactoring 461

Login

Recall that our login button’s onclick handler initiates a call to the login() method of our page, which in turn calls this method. The login command, at least from the perspective of the server, is a command that the server must handle by checking the credentials and then (if they are valid) responding with the same response that our load-page command would perform. With that in mind, let’s look at the implementation shown in listing 11.15.

Listing 11.15 The portal login method

login: function(userName, password) {

this.userName = userName; this.password = password;

if ( this.options.messageSpanId ) document.getElementById(

this.options.messageSpanId).innerHTML = "Verifying Credentials";

this.issuePortalCommand( Portal.LOGIN_ACTION, "user=" + this.userName, "pass=" + this.password );

},

The method puts a “Verifying Credentials” message into the span designated by our configurable option this.options.messageSpanId. It then issues a login command to the portal back end, passing the credentials that were passed into the method as request parameters. The issuePortalCommand() method we’ve just put in place does all the hard work.

Load settings

Recall that the createPortal() function of our page calls this method to load the initial configuration of our portal windows. The method to load the settings for the page is even simpler than the login method just discussed. It’s just a thin wrapper around our issuePortalCommand(). It passes the user as the lone parameter that the server uses to load the relevant window settings, since the settings are on a per-user basis:

loadPage: function(action) {

this.issuePortalCommand( Portal.LOAD_SETTINGS_ACTION, "user=" + this.userName, "pass=" + this.password );

},