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

Ajax Patterns And Best Practices (2006)

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

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

39

The Magic of the Asynchronous Class

Let’s focus on how the Asynchronous class solves the instance and callback problem. The specific code is illustrated again as follows:

function Asynchronous_call(url) { var instance = this;

this._xmlhttp.open('GET', url, true); this._xmlhttp.onreadystatechange = function() {

switch(instance._xmlhttp.readyState) { case 1:

instance.loading();

break; case 2:

instance.loaded();

break; case 3:

instance.interactive();

break; case 4:

instance.complete(instance._xmlhttp.status, instance._xmlhttp.statusText,

instance._xmlhttp.responseText, instance._xmlhttp.responseXML); break;

}

}

this._xmlhttp.send(null);

}

Asynchronous_call is associated with an instance of Asynchronous because of the prototype definition. Then when the HTML code calls asynchronous.call, the function Asynchronous_call is called and the this instance references the instantiated class. The variable this.xmlhttp is an instance of XMLHttpRequest, and the property onreadystatechange needs to be assigned a function. There is a peculiarity with JavaScript in that if a property is assigned the value of this.somefunction, then what is assigned is a function and not a function associated with a class instance, as was shown by the code that looked like it would work, but didn’t.

When the method Asynchronous_call is called, the this variable references an instance of Asynchronous. What is happening is that JavaScript is associating a function with an instance. Logically then, if the property onreadystatechange were assigning a function associated with an instance of Asynchronous, then when a callback is made, the this variable should reference an instance of Asynchronous. Figure 2-7 shows that there is no reference to an instance of

Asynchronous.

40

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

Figure 2-7. Debugger illustrating that a function does not reference a class instance

The debugger shown in Figure 2-7 is distributed with Mozilla, and in the middle window on the left side is a reference to the this variable. The watch window illustrates that this does not reference an instance and is a plain, simple ScriptFunction. This means that even though the original function was associated with an instance of Asynchronous, when used as a callback the reference disappears.

A solution would be to cross-reference a request with an Asynchronous instance that is stored in an array that is accessed to identify the request. Such a solution is complicated and relies on some global array.

The solution is not a complex cross-referencing algorithm, but the use of a unique implementation detail of JavaScript. Look back at the implementation of Asynchronous_call, illustrated briefly as follows:

function Asynchronous_call(url) { var instance = this;

this._xmlhttp.open('GET', url, true); this._xmlhttp.onreadystatechange = function() {

switch(instance._xmlhttp.readyState) {

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

41

First, the this variable is assigned to the instance variable The assignment is important because it is a variable that is managed by JavaScript. Second, the property onreadystatechange is assigned a dynamic anonymous function. An anonymous function is a function without an identifier, which contains only a signature and implementation. Using an anonymous function in the context of a function allows the referencing of variables in the anonymous function that were defined in the function itself. This means the variable instance is available for referencing in the anonymous function. What makes this feature a big deal is that when the anonymous function is called, the caller of Asynchronous_call will already have exited the function and be doing something else. The reason the local variable instance is still available is because JavaScript sees a reference and does not garbage-collect it until the this_xmlhttp instance is garbagecollected.

Putting all of this together in the HTML code, the Asynchronous property complete is assigned the functions AsyncUpdateEvent and AsyncUpdateEvent2. Whenever any of these functions are called, the this references a valid instance of Asynchronous. Then the code that was referencing myState, which should have worked, would work. Looking at the HTML code, you can see that the

AsyncUpdateEvent this references the variable asynchronous, and AsyncUpdateEvent2 this references the variable asynchronous2. Figure 2-8 shows the proof that the this variable is assigned.

Figure 2-8. Debugger illustrating that a function does reference a class instance

42

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

In Figure 2-8 the debugger shows that this references an instance of Asynchronous. In the example HTML code, the methods AsyncUpdateEvent and AsyncUpdateEvent2 do not use the this variable, but they could.

Now you’re ready to put it all together and execute the HTML code. Click the Get a Document button and then click the Get a Document2 button. The HTML page in Figure 2-9 is generated.

Figure 2-9. HTML page state after immediate feedback of the second row

In Figure 2-9 the second row contains data, whereas the first row does not. This is because the second row references a static document that is downloaded and processed immediately. The first row is not yet filled out because there is a 10-second delay. After 10 seconds, the HTML page appears similar to Figure 2-10.

Figure 2-10. HTML page state after all requests are finished

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

43

In Figure 2-10 the page is in its final state with both rows containing data. The processing occurred at different times, and the two requests ran concurrently. Using the defined Asynchronous class, multiple requests could be running at the same time.

Providing Feedback from Asynchronous Requests

When an HTML page makes an asynchronous request, the request will return immediately and the JavaScript will not know whether the request worked. Right after making the call, the JavaScript has to assume that the HTTP request worked. The feedback from the server to the JavaScript is a callback. Between the call and callback, 1 second, 10 seconds, or 3 minutes could transpire. If 3 minutes pass, the user will become impatient as nothing will be happening on the HTML page. If there is no feedback whatsoever, people get nervous and think something went wrong and will press the button again. This is why it is important to provide some form of feedback.

To provide feedback, a timer is used. The timer periodically checks the state of the HTTP request by querying the readyState property. While the user is waiting, a turning hour clock is generated or progress bar incremented. How you provide the feedback is up to you, but to provide feedback you will need a timer.

One-Shot Timers

A one-shot timer in JavaScript counts down a period of time and then executes some JavaScript. There is no repetition when using a one-shot timer. A one-shot timer is implemented by using the following HTML code:

<html>

<head>

<title>Sample Page</title>

<script language="JavaScript" type="text/javascript"> var counter = 0;

function StartIt() {

document.getElementById('result').innerHTML = "(" + counter + ")"; counter ++;

if( counter <= 10) { window.setTimeout("StartIt()", 1000);

}

}

</script>

</head>

<body>

<button onclick="StartIt()">One Shot Counter</button> <p><table border="1">

<tr><td>Counter</td><td><span id="result">No Result</span></td></tr> </table></p>

</body>

</html>

44

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

In the example HTML code, there is a button that when pressed calls the function StartIt. The function StartIt generates output in the HTML code of the variable counter. The variable counter is a counter that is incremented. To start the timer, the method window.setTimeout needs to be called. The method setTimeout starts a one-time timer that executes the JavaScript represented by the first parameter. The second parameter represents the number of milliseconds that should pass before the JavaScript is executed. It is important to realize that the JavaScript executed is a text-based script and should not reference variables that are not in scope.

To generate a repeating timer, the JavaScript calls the function StartIt. Then for each time-out (1 second), the timer countdown is started again. The timer is not started after the counter has reached a value of 10.

Periodic Timers

The other type of timer is a periodic timer that executes every n milliseconds. Using a periodic timer in JavaScript is similar to using a one-shot timer except the method call is different. Following is the HTML code used to run a periodic timer:

<html>

<head>

<title>Sample Page</title>

<script language="JavaScript" type="text/javascript"> var intervalId;

var counter2 = 0;

function NeverEnding(input) { document.getElementById('result').innerHTML =

"(" + input + ")(" + counter2 + ")"; counter2 ++;

if( counter2 > 10) { window.clearInterval(intervalId);

}

}

function StartItNonEnding() {

intervalId = window.setInterval(NeverEnding, 1000, 10);

}

</script>

</head>

<body>

<button onclick="StartItNonEnding()">Get a document</button> <p><table border="1">

<tr><td>Counter</td><td><span id="result">No Result</span></td></tr> </table></p>

</body>

</html>

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

45

In this example, the button calls the function StartItNonEnding. In the function

StartItNonEnding, there is a single method call, window.setInterval. The method setInterval has multiple variations, and a valid variation is like setTimeout illustrated previously. The variation illustrated in the HTML code uses three parameters, even though only two are necessary. The first parameter is a reference to a function that is called for each periodic event. The second parameter is the length of the period. And the third parameter is an argument that is passed to the function NeverEnding. The third parameter does not work in Internet Explorer, but works on other browsers such as Firefox and Safari.

As in the one-shot timer, the timer output is inserted into the HTML document. The counter is incremented for each call to the function NeverEnding. What is different is that NeverEnding has a parameter that can be used to uniquely identify an instance of the timer. To stop a periodic timer, the method clearInterval is used. The parameter for clearInterval is the value of the instantiated timer that is returned when calling the method setInterval.

After running the HTML code, the generated output is similar to Figure 2-11. The value 10 in the lower-right corner of the HTML table is the value passed to the function NeverEnding. The 0 value is the counter.

Figure 2-11. Generated HTML document

Calling Domains Other Than the Serving Domain

When an HTML page is downloaded from one domain, the XMLHttpRequest object can download content only from that domain. So if the page is downloaded from devspace.com, content can be downloaded only from devspace.com. Attempting to download content from another domain will generate an error similar to that in Figure 2-12—regardless of the browser.

The error is permission related and is a consequence of the same origin policy, and not a programmatic error. A permission error indicates that something is being attempted that may be possible under different circumstances. The error is used to prevent the cross-site scripting vulnerability. What needs to be modified are the permissions on the browser.

46

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

Figure 2-12. Generated error after attempting to load content from another domain

Before learning how to change permissions to get around the same origin policy, you need to understand what the policy is. Let’s say that I retrieve a document from the server http:// localhost:8080/chap02/factory.html. The same origin policy states that only requests to the same origin can be retrieved. The defined origin is the protocol http, and the host localhost with the port 8080. If any of these values change, any document that is referenced will result in a permission exception. The file http://localhost:8080/rest/cgross/books.xml could be downloaded. The same origin policy exists so that other sites cannot be referenced, as many hackers have used the technique for their malware.

Apple Safari

Using the Apple Safari browser is a problem in that there is no way to get around the same origin policy. The browser does not have any preferences that can be used to assign trust to a site or web page. Nor is it possible to sign an HTML page to allow cross-domain HTTP requests, or at least that was the status at the time of this writing.

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

47

Microsoft Internet Explorer

Microsoft Internet Explorer is one of the two browsers mentioned in this book that allow crossdomain HTTP requests if the permission has been granted. Internet Explorer grants permissions only if the site has been assigned as trusted. An algorithm is implemented so that trusted sites do not apply the Same Origin Policy.

So, for example, to set the site http://192.168.1.101:8080 as trusted, you would use the following steps:

1.Open Microsoft Internet Explorer and from the menu select Tools Internet Options. A dialog box similar to Figure 2-13 is generated.

Figure 2-13. Internet Options dialog box used to define a trusted site

2.Select the Security tab and then the Trusted Sites icon, resulting in a dialog box similar to Figure 2-14.

48

C H A P T E R 2 T H E N U T S A N D B O L T S O F A J A X

Figure 2-14. Security tab and Trusted Sites icon selected

3. Click the Sites button, and the dialog box changes, as shown in Figure 2-15.

Figure 2-15. Dialog box used to define a trusted site