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

Some small refactoring case studies

83

 

 

Register

Observer

Unregister

Observable

Notify

Maintain list of registered Observers

Responsibility of Observer Responsibility of Observable

Figure 3.2

Division of responsibility in the Observer pattern. Objects wishing to be notified of an event, the Observers, can register and unregister themselves with the event source, Observable, which will notify all registered parties when an event occurs.

Provided that every subsystem uses this approach, we can offer a much cleaner way of setting up all the subsystems without tangling them up in one another. Of course, it takes only one rogue piece of code to directly override window.onload and the system will break. But we have to take charge of our codebase at some point to prevent this from happening.

It’s worth pointing out here that the newer W3C event model also implements a multiple event handler system. We’ve chosen to build our own here on top of the old JavaScript event model because implementations of the W3C model aren’t consistent across browsers. We discuss this in greater detail in chapter 4.

The design pattern into which our code here is refactored is called Observer. Observer defines an Observable object, in our case the built-in window object, and a set of Observers or Listeners that can register themselves with it (figure 3.2).

With the Observer pattern, responsibility is apportioned appropriately between the event source and the event handler. Handlers take responsibility for registering and unregistering themselves. The event source takes responsibility for maintaining a list of registered parties and firing notifications when the event occurs. The pattern has a long history of use in event-driven UI programming, and we’ll return to Observer when we discuss JavaScript events in more detail in chapter 4. And, as we’ll see, it can also be used in our own code objects independently of the browser’s mouse and key event processing.

For now, let’s move on to the next recurring issue that we can solve through refactoring.

3.2.3Reusing user action handlers: Command pattern

It may be obvious to say that in most applications, the user is telling (through mouse clicks and keyboard presses) the app to do something, and the app then

does it. In a simple program, we might present the user with only one way to

84CHAPTER 3

Introducing order to Ajax

perform an action, but in more complex interfaces, we will often want the user to be able to trigger the same action from several routes.

Implementing a button widget

Let’s say that we have a DOM element styled to look like a button widget that performs a calculation when pressed and updates an HTML table with the result. We could define a mouse-click event-handler function for the button element that looks like this:

function buttonOnclickHandler(event){ var data=new Array();

data[0]=6;

data[1]=data[0]/3;

data[2]=data[0]*data[1]+7;

var newRow=createTableRow(dataTable); for (var i=0;i<data.length;i++){

createTableCell(newRow,data[i]);

}

}

We’re assuming here that the variable dataTable is a reference to an existing table and that the functions createTableRow() and createTableCell() take care of the details of DOM manipulation for us. The interesting thing here is the calculation phase, which could, in a real-world application, run to hundreds of lines of code. We assign this event handler to the button element like so:

buttonDiv.onclick=buttonOnclickHandler;

Supporting multiple event types

Let’s say that we have now supercharged our application with Ajax. We are polling the server for updates, and we want to perform this calculation if a particular value is updated from the server, too, and update a different table with the data. We don’t need to go into the details of setting up a repeated polling of the server here. Let’s assume that we have a reference to an object called poller. Internally, it is using an XMLHttpRequest object and has set its onreadystatechange handler to call an onload function whenever it has finished loading an update from the server. We could abstract out the calculation and display phases into helper functions, like this:

function buttonOnclickHandler(event){ var data=calculate(); showData(dataTable,data);

}

function ajaxOnloadHandler(){ var data=calculate();

Some small refactoring case studies

85

 

 

showData(otherDataTable,data);

}

function calculate(){ var data=new Array(); data[0]=6; data[1]=data[0]/3;

data[2]=data[0]*data[1]+7; return data;

}

function showData(table,data){

var newRow=createTableRow(table); for (var i=0;i<data.length;i++){

createTableCell(newRow,data[i]);

}

}

buttonDiv.onclick=buttonOnclickHandler;

poller.onload=ajaxOnloadHandler;

A lot of the common functionality has been abstracted out into the calculate() and showData() functions, and we’re only repeating ourselves a little in the onclick and onload handlers.

We’ve achieved a much better separation between the business logic and the UI updates. Once again, we’ve stumbled upon a useful repeatable solution. This time it is known as the Command pattern. The Command object defines some activity of arbitrary complexity that can be passed around in code easily and swapped between UI elements easily. In the classic Command pattern for objectoriented languages, user interactions are wrapped up as Command objects, which typically derive from a base class or interface. We’ve solved the same problem in a slightly different way here. Because JavaScript functions are first-class objects, we can treat them as Command objects directly and still provide the same level of abstraction.

Wrapping up everything that the user does as a Command might seem a little cumbersome, but it has a hidden payoff. When all our user actions are wrapped up in Command objects, we can easily associate other standard functionality with them. The most commonly discussed extension is to add an undo() method. When this is done, the foundations for a generic undo facility across an application are laid. In a more complex example, Commands could be recorded in a stack as they execute, and the user can use the undo button to work back up the stack, returning the application to previous states (figure 3.3).

Each new command is placed on the top of the stack, which may be undone item by item. The user creates a document by a series of write actions. Then she selects the entire document and accidentally hits the delete button. When she invokes the undo function, the topmost item is popped from the stack, and its

86CHAPTER 3

Introducing order to Ajax

Command stack

Delete selected

Interactions

Select all

Delete selected

Write para 2

 

Write para 1

 

 

Command stack

Undo

Select all

Document preview

 

Write para 2

Blah blah

Write para 1

blah blah

Blah blah

 

blah blah

 

rhubarb

 

blah

 

Document preview

Figure 3.3 Using the Command pattern to implement a generic undo stack in a word processing application. All user interactions are represented as commands, which can be undone as well as executed.

undo() method is called, returning the deleted text. A further undo would deselect the text, and so on.

Of course, using Command to create an undo stack means some extra work for the developer, in ensuring that the combination of executing and undoing the command returns the system to its initial state. A working undo feature can be a strong differentiator between products, however, particularly for applications that enjoy heavy or prolonged use. As we discussed in chapter 1, that’s exactly the territory that Ajax is moving into.

Command objects can also be useful when we need to pass information across boundaries between subsystems in an application. The network, of course, is just such a boundary, and we’ll revisit the Command pattern in chapter 5, when we discuss client/server interactions.