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

Beginning JavaScript With DOM Scripting And Ajax - From Novice To Professional (2006)

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

326

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

Now start to flesh out the skeleton:

selectBoxes.js

dynSelect = {

AJAXlabel : 'Only reload the results, not the whole page', AJAXofferClass : 'ajax',

containerID : 'formOutput', backlinkID : 'back',

The init() method tests whether the W3C DOM is supported, retrieves the first form, and stores the Submit button with the ID select in a property—this is necessary to remove the button on the last step. It then creates a new paragraph and applies the class for the Ajax trigger defined earlier.

selectBoxes.js (continued)

init : function(){

if( !document.getElementById || !document.createTextNode ){ return;

}

var f = document.getElementsByTagName( 'form' )[0]; dynSelect.selectButton = document.getElementById( 'select' ); var p = document.createElement( 'p' );

p.className = dynSelect.AJAXofferClass;

Next on the agenda is the check box to offer the option to turn on Ajax. Set the name and ID of the check box to xhr and determine whether the current URI already has the ?ajax search string. If it has, preset the check box to already selected (this is necessary to ensure that the link back to the first step does not stop the Ajax enhancements from working).

selectBoxes.js (continued)

dynSelect.cb = document.createElement( 'input' ); dynSelect.cb.setAttribute( 'type', 'checkbox' ); dynSelect.cb.setAttribute( 'name', 'xhr' ); dynSelect.cb.setAttribute( 'id', 'xhr' );

if( window.location.search != '' ) { dynSelect.cb.setAttribute( 'defaultChecked', 'checked' ); dynSelect.cb.setAttribute( 'checked', 'checked' );

}

Add the check box to the new paragraph, and a label with the appropriate text following it. The new paragraph becomes the first child node of the form, and you apply an event handler that triggers the dohxhr() method when the form is submitted.

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

327

selectBoxes.js (continued)

p.appendChild( dynSelect.cb );

var lbl = document.createElement( 'label' ); lbl.htmlFor = 'xhr';

lbl.appendChild( document.createTextNode( dynSelect.AJAXlabel ) ); p.appendChild( lbl );

f.insertBefore( p, f.firstChild );

dynSelect.addEvent(f, 'submit', dynSelect.doxhr, false );

},

The dohxr() method tests whether the check box has been ticked and simply returns when it isn’t. If it is, you define two variables for the current airport and the current destination and store the output element in a property. You test whether the output container exists and return if it doesn’t.

selectBoxes.js (continued)

doxhr : function( e ) {

if( !dynSelect.cb.checked ){ return; } var airportValue, destinationValue;

dynSelect.outputContainer = document.getElementById( dynSelect.containerID );

if( !dynSelect.outputContainer ){ return; }

Here is the XHR code, defining the correct object and setting the onreadystage event listener.

selectBoxes.js (continued)

var request; try {

request = new XMLHttpRequest(); } catch( error ) {

Try {

request = new ActiveXObject( "Microsoft.XMLHTTP" );

}catch( error ) { return true;

}

}

request.onreadystatechange = function() { if( request.readyState == 1 ) {

dynSelect.selectButton.value = 'loading...';

}

328

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

if( request.readyState == 4 ) {

if( request.status && /200|304/.test( request.status ) ) { dynSelect.retrieved( request );

}else{

dynSelect.failed( request );

}

}

}

Determine whether the document contains the airport and destination select boxes; if so, store their current states in the variables airportValue and destinationValue. Notice that you need to check the type of the airport field in the second stage of the flight selection process, since it is a hidden field.

selectBoxes.js (continued)

var airport = document.getElementById( 'airport' ); if( airport != undefined ) {

if( airport.nodeName.toLowerCase() == 'select' ) { airportValue = airport.options[airport.selectedIndex].value;

}else {

airportValue = airport.value;

}

}

var destination = document.getElementById( 'destination' ); if( destination ) {

destinationValue = destination.options [destination.selectedIndex].value;

}

Since the form is sent using POST and not GET, you need to define the request a bit differently. First of all, you need to assemble the request parameters as a string (this is the trail of variables on the URI when the send method is GET, e.g., http://www.example.com/ index.php?search=DOM&values=20&start=10).

selectBoxes.js (continued)

var parameters = 'airport=' + airportValue; if( destinationValue != undefined ) {

parameters += '&destination=' + destinationValue;

}

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

329

Next, open the request. In addition to the modified header to prevent caching, you also need to tell the server that the content type is application/x-www-form-urlencoded; then you transmit the length of all the request parameters as the value to accompany Content-length. You also need to tell the server to close the connection once it has finished retrieving all the data. Unlike GET requests, send() needs a parameter when you POST, which is the URI-encoded parameters.

selectBoxes.js (continued)

request.open( 'POST', 'selectBoxes.php' ); request.setRequestHeader( 'If-Modified-Since', 'Wed, 05 Apr 2006 00:00:00 GMT' ); request.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );

request.setRequestHeader( 'Content-length', parameters.length ); request.setRequestHeader( 'Connection', 'close' );

request.send( encodeURI( parameters ) );

Note Don’t beat yourself up if you don’t know all that is going on here; it is after all server and HTTP code, and you are just starting with JavaScript. Chances are you will never really have to grasp what all that means, as long as you use it this way.

If you are on the page before the last one and both an airport and a destination are available, remove the Submit button to prevent errors.

Note This is a cosmetic step for this example. A real application should work through the following steps, too, but you don’t need to go that far now.

Finally, invoke cancelClick() to prevent normal form submission.

selectBoxes.js (continued)

if( airport && destination ) {

var sendButton = document.getElementById( 'select' ); sendButton.parentNode.removeChild( sendButton );

}

dynSelect.cancelClick( e );

},

330

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

The retrieved() method doesn’t differ much from the other examples. Undo what you have done in the previous step by changing the Submit button’s value back to Select before retrieving the responseText of the request and replacing the old form elements with the new ones. Add ?ajax to the href of the link pointing back to the first step to make sure that activating this link will not turn off the previously selected functionality (by now you know the user wants the Ajax interface).

selectBoxes.js (continued)

retrieved : function( requester, e ) { dynSelect.selectButton.value = 'Select'; var content = requester.responseText;

dynSelect.outputContainer.innerHTML = content;

var backlink = document.getElementById( dynSelect.backlinkID ); if( backlink ) {

var url = backlink.getAttribute( 'href' ); backlink.setAttribute( 'href', url+'?ajax' );

}

dynSelect.cancelClick( e );

},

The rest of the script consists of the familiar failed(), cancelClick(), and addEvent() utility methods.

selectBoxes.js (continued)

failed : function( requester ){

alert('The XMLHttpRequest failed. Status: ' + requester.status); return true;

},

cancelClick : function( e ){ [... code snipped ...]

},

addEvent: function( elm, evType, fn, useCapture ){ [... code snipped ...]

}

}

dynSelect.addEvent( window, 'load', dynSelect.init, false );

This example shows that Ajax is very dependent on server code. If you know what you will get back, then it is easy to create a useful and attractive interface.

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

331

You can also use the Ajax approach in an unobtrusive and optional manner to make older effects more eye-catching and targeted better to those who want them. In the previous chapter, you developed a dynamic navigation that collapsed and expanded nested links, and I promised you that we’d come back to that example, which we do in the following section.

Optional Dynamic Ajax Menus

One of the problems dynamic navigation has is that you may offer the user too many choices. It is pretty tempting to consider a menu easy to use when you can see it collapsed, and you can collapse and expand sections to navigate through it, but it is a different story when you turn off JavaScript and CSS and you see all the links at once. Now also consider the kind of visitors who listen to your web site because they cannot see, or those who need to zoom only a part of the screen to navigate around the page.

Wouldn’t it be much more useful to give visitors the choice to enable an enhanced dynamic menu and give only the minimum necessary menu structure to those who choose otherwise?

Recall the menu structure you used in the previous chapter:

navigation.php

<ul id="nav">

<li><a href="index.php">Home</a></li> <li><a href="products.php">Products</a>

<ul>

<li><a href="cms.php">CMS solutions</a> <ul>

<li><a href="minicms.php">Mini CMS</a></li>

<li><a href="ncc1701d.php">Enterprise CMS</a></li> </ul>

</li>

<li><a href="portal.php">Company Portal</a></li> <li><a href="mailserver.php">eMail Solutions</a>

<ul>

<li><a href="privatemail.php">Private POP3/SMTP</a></li> <li><a href="lists.php">Listservers</a></li>

</ul>

</li>

</ul>

</li>

<li><a href="services.php">Services</a>

332

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

<ul>

<li><a href="training.php">Employee Training</a></li> <li><a href="audits.php">Auditing</a></li>

<li><a href="bulkmail.php">Bulk sending/email campaigns</a></li> </ul>

</li>

<li><a href="pricing.php">Pricing</a></li> <li><a href="about_us.php">About Us</a>

<ul>

<li><a href="our_offices.php">Our offices</a></li> <li><a href="our_people.php">Our people</a></li> <li><a href="vacancies.php">Jobs</a></li>

<li><a href="partners.php">Industry Partners</a></li> </ul>

</li>

<li><a href="contact.php">Contact Us</a> <ul>

<li><a href="snail.php">Postal Addresses</a></li> <li><a href="callback.php">Arrange Callback</a></li>

</ul>

</li>

</ul>

I’ve already said that displaying the menu item corresponding to the current page as a STRONG element is a very good idea. Let’s take that idea further and strip down the menu to what is actually necessary. What this means is that you’ll remove all the nested elements from menu items other than the current one (or its parent). For example, on the company portal page this would be as follows:

<ul id="nav">

<li><a href="index.php">Home</a></li> <li><a href="products.php">Products</a>

<ul>

<li><a href="cms.php">CMS solutions</a></li>

<li><strong>Company Portal</strong></li>

<li><a href="mailserver.php">eMail Solutions</a></li> </ul>

</li>

<li><a href="services.php">Services</a></li> <li><a href="pricing.php">Pricing</a></li> <li><a href="about_us.php">About Us</a></li> <li><a href="contact.php">Contact Us</a></li>

</ul>

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

333

For a top-level item like that corresponding to the products page, the code will be even less:

<ul id="nav">

<li><a href="index.php">Home</a></li>

<li><strong>Products</strong></li>

<li><a href="services.php">Services</a></li> <li><a href="pricing.php">Pricing</a></li> <li><a href="about_us.php">About Us</a></li> <li><a href="contact.php">Contact Us</a></li>

</ul>

You could easily do this in JavaScript by now; however, that would defeat the purpose of the exercise, as visitors without JavaScript would still get the full menu.

Instead, you’ll use a server-side script in PHP that does this for you. On every page this script

Loads the navigation template

Checks through all the links and replaces the one that matches the current file name with a STRONG element (via regular expressions)

Checks whether there is an ?ajax parameter sent via GET and if not removes all nested lists that are not inside an LI item that also contains a STRONG element

We will not go into the details of the script here, as this is not a PHP book; if you want to have a look, the script is called globals.php and is located in the navigation folder of the code download for this chapter.

Using this script, you can offer users a fully functional menu without any unnecessary links, as shown in Figure 8-9.

Figure 8-9. The basic navigation without JavaScript

334

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

You can now use JavaScript to offer the user a more advanced navigation menu that shows the complete site map and loads the content via XHR instead of reloading the whole page. To achieve this, you can reuse the dynamic navigation script developed in the previous chapter and add one line that triggers the collapsing and expanding functionality only when there is a search parameter called ajax in the URI for the current page:

siteNavigationIndicator.js (changes)

sn={

init : function() {

if( window.location.search.indexOf( 'ajax' ) == -1 ){ return; }

[... code snipped ...]

},

The main script will have to do the following:

Add a new list item to the navigation with a link that allows the user to turn Ajax functionality on or off (select a basic or an enhanced navigation).

If there is no ajax parameter in the URI, stop and let the back end create and change the navigation.

Otherwise, attach event handlers on all the links in the navigation, loading the linked documents in an output container via XHR instead of reloading the page.

Replace the old STRONG element with a link pointing to the right document and the XHR handler when another link is activated.

Replace that other link with a STRONG element and expand and highlight the menu section in which the link is located.

Note This means that you need to know which document the links replacing the STRONG elements should point to. You can do this by storing the file name (without the .php extension, since class names cannot contain full stops) in the class attribute of the STRONG. This is not the cleanest of options, but other than using invalid HTML with a made-up attribute, it is the only way. The PHP script provides you with that functionality on the highlighted menu item.

You start with a bunch of properties—the ID of the menu, the trigger as a blank property, and its ID and labels for both states. The trigger will become a list item in the menu that turns the Ajax functionality and the enhanced navigation on or off. Depending on the state, the different labels will be the link text. Furthermore, you define the ID of the output element and the message displayed when the XHR is loading the content.

C H A P T E R 8 B A C K - E N D I N T E R A C T I O N W I T H A J A X

335

XHRSiteNav.js

Xhrsitenav = { navID : 'nav', trigger : null,

triggerID : 'AJAXtrigger',

triggerLabel : 'Switch to advanced navigation', downLabel : 'Switch to basic navigation', output : 'content',

loadingMessage : '<img src="../indicator_big.gif" alt="loading..." />',

The init() method needs to test for all the necessary elements, call createTrigger() to add the link to the menu, and not do anything else when there is no ajax search parameter in the current URL.

XHRSiteNav.js (continued)

init : function() {

if( !document.getElementById || !document.createTextNode ) { return;

}

xhrsitenav.nav = document.getElementById( sn.navID ); if( !xhrsitenav.nav ) { return; } xhrsitenav.outputContainer = document.getElementById( xhrsitenav.output );

if( !xhrsitenav.outputContainer ){ return; } xhrsitenav.createTrigger();

if( window.location.search.indexOf( 'ajax' ) == -1 ){ return; }

Loop through all the links but the last one and those links having only a hash mark (#) as the href attribute; assign a click event handler pointing to the xhr method for each one. Since the last link is the trigger, you need to skip the last one; the links having only # as their href attribute values are the ones added by the dynamic menu script.

XHRSiteNav.js (continued)

var navlinks = xhrsitenav.nav.getElementsByTagName( 'a' ); for( var i = 0; i < navlinks.length - 1; i++ ){

if( navlinks[i].href == '#' ){ continue; }

DOMhelp.addEvent( navlinks[i], 'click', xhrsitenav.xhr, false ); navlinks[i].onclick = DOMhelp.safariClickFix;

}