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

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

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

266

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

You use one method to initialize the script, one to generate the extra links and elements that you need, one to navigate around the “pages” (that is, hide the current result set and show the next), one to show the current page, and one to alter the pagination menu.

init : function(){},

createPaginationNav : function( table ){}, navigate : function( e ){},

showSection : function( table, start ){}, changePaginationNav : function( table, start ){}

}

DOMhelp.addEvent( window, 'load', pn.init, false );

Grab a coffee and some cookies, as this is quite a script ahead of you; don’t worry though— most of it is simple logic.

The init() method checks whether DOM is supported and starts looping through all table elements in the document. It tests whether the table has the right class (defined in the pn.paginationClass property) and whether it has more rows than you want to show on each “page” (defined in the pn.increase property). If one of these is not the case, it skips the rest of the method—effectively not adding any menu.

pagination.js (excerpt)

init : function() { var tablebody;

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

}

var ts = document.getElementsByTagName( 'table' ); for(var i = 0;i < ts.length; i++ ){

if( !DOMhelp.cssjs( 'check', ts[i], pn.paginationClass ) ){

continue;

}

if( ts[i].getElementsByTagName( 'tr' ).length < pn.increase+1 ){

continue;

}

As the data rows you want to hide don’t include the header row, but only those contained in the table body, you need to tell the other methods this.

The easiest option is to store only the relevant rows in a property of the table. You grab the first TBODY in the table and store all of its rows in a datarows property. You also store the number of all rows in datarowsize and initialize the current property as null.

This property will store the start of the page you want to show. By storing the rows and the number of rows as properties, you make it easier for other methods to retrieve information from the table without having to read out this information again from the DOM.

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

267

pagination.js (excerpt)

tablebody = ts[i].getElementsByTagName( 'tbody' )[0]; ts[i].datarows = tablebody.getElementsByTagName( 'tr' ); ts[i].datarowsize = ts[i].datarows.length;

ts[i].current = null;

Apply the dynamic class to the table and thus hide all the table rows. Call the createPaginationNav() method with a reference to the current table as a parameter to add the “previous” and “next” links, and call showSection() with the table reference and 0 as parameters to show the first result set.

pagination.js (excerpt)

DOMhelp.cssjs( 'add', ts[i], pn.dynamicClass ); pn.createPaginationNav( ts[i] ); pn.showSection( ts[i], 0 );

}

},

The createPaginationNav() method does not contain any surprises; all it does is create the links and the counter and add the event handlers pointing to the navigate() method. You start by creating a new paragraph element and adding the pagination menu class to it.

pagination.js (excerpt)

createPaginationNav : function( table ){ var navBefore, navAfter;

navBefore = document.createElement( 'p' ); DOMhelp.cssjs('add', navBefore, pn.paginationMenuClass );

Add a new link to the paragraph with the previousLabel property value as the text content and a new SPAN element that will display the number of the current result set in between the “previous” and “next” links. You preset the counter with 1 as the start value, the number of elements shown on each page as defined in pn.increase as the end value, and the number of all data rows as the total number. The last element to add to the new paragraph is the “next” link. You add the new paragraph immediately before the table via parentNode and insertBefore().

pagination.js (excerpt)

navBefore.appendChild( DOMhelp.createLink( '#', pn.previousLabel ) );

navBefore.appendChild( document.createElement( 'span' ) ); counter=pn.counter.replace( '_x_', 1 ); counter=counter.replace( '_y_', pn.increase ); counter=counter.replace( '_z_', table.datarowsize-1 );

navBefore.getElementsByTagName( 'span' )[0].innerHTML = counter; navBefore.appendChild( DOMhelp.createLink( '#', pn.nextLabel ) ); table.parentNode.insertBefore( navBefore, table );

268

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

It would be good to show the same menu below the table as well. Instead of re-creating all these elements once more, it is enough to clone the paragraph and insert it after the table via parentNode, insertBefore(), and nextSibling. Then, store the “previous” and “next” links of each paragraph as their own table properties to make it easier to change them in other methods.

pagination.js (excerpt)

navAfter = navBefore.cloneNode( true );

table.parentNode.insertBefore( navAfter, table.nextSibling ); table.topPrev = navBefore.getElementsByTagName( 'a' )[0]; table.topNext = navBefore.getElementsByTagName( 'a' )[1]; table.bottomPrev = navAfter.getElementsByTagName( 'a' )[0]; table.bottomNext = navAfter.getElementsByTagName( 'a' )[1];

You couldn’t apply the event handlers earlier, as cloneNode() does not clone any handlers. Now you can apply all the handlers and the fixes for Safari to each of the links. The last change for this method is to store the counters in properties to make it easier for the other methods to update them.

pagination.js (excerpt)

DOMhelp.addEvent( table.topPrev, 'click', pn.navigate, false); DOMhelp.addEvent( table.bottomPrev, 'click', pn.navigate, false); DOMhelp.addEvent( table.topNext, 'click', pn.navigate, false); DOMhelp.addEvent( table.bottomNext, 'click', pn.navigate, false); table.bottomNext.onclick = DOMhelp.safariClickFix; table.topPrev.onclick = DOMhelp.safariClickFix; table.bottomPrev.onclick = DOMhelp.safariClickFix; table.topNext.onclick = DOMhelp.safariClickFix;

table.topCounter = navBefore.getElementsByTagName( 'span' )[0]; table.bottomCounter = navAfter.getElementsByTagName( 'span' )[0];

},

The event listener method navigate() needs to check which link invoked it. The first step is to retrieve the event target via getTarget() and make sure it is a link by comparing its node name with A (remember, Safari likes to send the text node inside the link as the event target instead).

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

269

pagination.js (excerpt)

navigate : function( e ){ var start, table;

var t = DOMhelp.getTarget( e );

while( t.nodeName.toLowerCase() != 'a' ){ t = t.parentNode;

}

It then needs to check whether the link is active or not by testing whether it has an href attribute (you will later turn the “next” or “previous” link off by removing the href attribute). If there is none, it shouldn’t do anything. The next task is to find the table from the link that is activated. As there is navigation above and below the table, you need to check whether either the previous or the next sibling node has the node name of table and define the variable table accordingly.

pagination.js (excerpt)

if( t.getAttribute( 'href' ) == null || t.getAttribute( 'href' ) == '' ){ return; } if( t.parentNode.previousSibling &&

t.parentNode.previousSibling.nodeName. toLowerCase() == 'table' ) {

table = t.parentNode.previousSibling;

}else {

table = t.parentNode.nextSibling;

}

Then determine whether the activated link was either one of the “next” links or one of the “previous” links, and define start as the current property of the table plus or minus the defined increase accordingly. You call showSection() with the retrieved table and the start values as parameters.

pagination.js (excerpt)

if(t == table.topNext || t == table.bottomNext ){ start = table.current + pn.increase;

} else if ( t == table.topPrev || t == table.bottomPrev ){ start = table.current - pn.increase;

}

pn.showSection( table, start );

},

270

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

The showSection() method calls the changePaginationNav() method to update the links and the counter and tests whether there is already a current parameter on the table. If there is one, this means data rows exist that need to be removed. You get rid of them by looping through the section of the data rows stored in the datarows property and removing the CSS class defined in showClass().

pagination.js (excerpt)

showSection : function( table, start ){ var i;

pn.changePaginationNav( table, start ); if( table.current != null ){

for( i=table.current; i < table.current+pn.increase; i++ ){ if( table.datarows[i] ) {

DOMhelp.cssjs( 'remove', table.datarows[i], pn.showClass );

}

}

}

You then loop from start to start plus the predefined increase, and add the CSS class to show these rows in the table. Notice that you need to test whether these rows exist; otherwise, you might try to reach rows that aren’t there on the last page. (Imagine a list of 22 elements; clicking the “next” link on the 16–20 page would try to show elements 21 to 25). To make sure the right slice gets shown the next time the method gets called, all that is left is to define the current property as the start value.

pagination.js (excerpt)

 

for( i = start; i < start

+ pn.increase; i++ ){

if( table.datarows[i] )

{

DOMhelp.cssjs( 'add',

table.datarows[i], pn.showClass );

}

 

}

 

table.current = start;

 

},

 

As hinted earlier, the changePaginationNav() method renders the “previous” link on the first page and the “next” link on the last page inactive. The trick to making links appear but not clickable is to remove the href attribute.

On the first page, the start value minus the predefined increase would result in a negative number, which is easy to test. When the number is larger than 0, you add the href attribute again.

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

271

pagination.js (excerpt)

changePaginationNav : function( table, start ){ if(start - pn.increase < 0 ) {

table.bottomPrev.removeAttribute( 'href' ); table.topPrev.removeAttribute( 'href' );

}else {

table.bottomPrev.setAttribute( 'href', '#' ); table.topPrev.setAttribute( 'href', '#' );

}

If start plus the increase is bigger than the number of rows, you need to remove the “next” link; otherwise, you need to make it active.

pagination.js (excerpt)

if(start + pn.increase > table.rowsize - 2 ) { table.bottomNext.removeAttribute( 'href' ); table.topNext.removeAttribute( 'href' );

}else{

table.bottomNext.setAttribute( 'href', '#' ); table.topNext.setAttribute( 'href', '#' );

}

Update the counter with the appropriate values (start needs a 1 added to it to make it easier to understand for humans, and you need to test that the last value is not larger than the number of existing rows), and you have created a paginated interface from a normal data table.

pagination.js (excerpt)

var counter = pn.counter.replace( '_x_', start+1 ); var last = start + pn.increase;

if( last > table.datarowsize ){ last = table.datarowsize; } counter = counter.replace( '_y_', last )

counter = counter.replace( '_z_', table.datarowsize ) table.topCounter.innerHTML = counter; table.bottomCounter.innerHTML = counter;

}

}

DOMhelp.addEvent( window, 'load', pn.init, false );

The logic of pagination stays the same, even if you decide to show and hide list items or other HTML constructs. You can make it even more complex by showing numbered steps in between the “previous” and the “next” links instead of the counter, but I leave that to you to have a go at it.

272

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

Summary of Navigation with JavaScript

Powering a site’s navigation with JavaScript is a very tempting use of your skills, as it is “out there” and still quite amazing for “wowing” clients with a fancy interface. The main thing to remember is that you should turn off JavaScript from time to time and see whether your interface still works. The same applies to not using your mouse but trying the keyboard instead.

You can use JavaScript to make large amounts of data like deeply nested navigation menus easier to grasp and presented to the user in bite-size chunks. However, don’t forget that some users will get all of the navigation without your script cutting it up into smaller servings. It might be a good idea to make menu interfaces that use the whole site map as their data source optional rather than a given. We will take a look at how to do that in the next chapter.

Things to Remember About JavaScript Navigation

Don’t send the user to another location or send form data without him clicking or activating an interface element. It is more confusing than helpful and may even be considered a security threat (if you can do it, anyone can send the user to a site).

Hiding data does not make it disappear; while you can make a lot of data easy to digest with a slick interface, some users will still get the whole lot in one serving, and all users— including those on slow connections—will have to download all of the data.

It is much safer to piggyback on existing web navigation patterns than to invent new ones. For example, it was quite easy to turn a working in-page navigation using links and anchors into a tabbed interface. Creating all the necessary tabs via JavaScript would have been a lot more hassle.

Forms and JavaScript

On the following pages, you’ll learn how to access, read, and change forms and their elements. We will not go into the details of validating forms here, as I have devoted Chapter 9 to the topic of data validation.

We will, however, touch on basic form usability as well as some bad practices and why they should be avoided. First of all, let’s take a look at the form used in some examples in this chapter and later on in the book:

exampleForm.html (excerpt)

<form method="post" action="send.php"> <fieldset>

<legend>About You</legend>

<p><label for="Name">Your Name</label></p> <p><input type="text" id="Name" name="Name" /></p> <p><label for="Surname">Your Surname</label></p>

<p><input type="text" id="Surname" name="Surname" /></p> <p><label for="email">Your email</label></p>

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

273

<p><input type="text" id="email" value="you@example.com" name="email" /></p>

</fieldset>

<fieldset>

<legend>Your message</legend>

<p><label for="subject">Subject</label> <select id="subject" name="subject">

<option value="generalEnquiry" selected="selected"> General question</option>

<option value="Webdesign">Webdesign</option> <option value="Hosting">Hosting</option> <option value="Training">Training</option>

<option value="Partnership">Partnership</option> <option value="other">Other</option>

</select></p>

<p><label for="otherSubject">specify other subject</label> <input type="text" id="otherSubject" name="otherSubject" /></p> <p><label for="Message">Your Message</label></p>

<p><textarea id="Message" name="Message" cols="20" rows="5"></textarea></p> </fieldset>

<fieldset>

<legend>Email options</legend>

<p><input type="checkbox" name="copyMeIn" id="copyMeIn" /> <label for="copyMeIn">Send me a copy of this email to

the above address</label></p>

<p><input type="checkbox" name="newsletter" value="yes" id="newsletter" />

<label for="newsletter">Sign me up for the newsletter</label></p> <p>Newsletter format:

<input type="radio" name="newsletterFormat" id="newsHtml" value="html" checked="checked" />

<label for="newsHTML">HTML</label>

<input type="radio" name="newsletterFormat" id="newsPlain" value="plain"/>

<label for="newsPlain">Text</label></p>

<p class="submit"><input type="submit" value="Send Form" /></p> </fieldset>

</form>

Note As you can see, this is a valid form for a HTML 4 STRICT document, and all elements are closed for XHTML compliance should you want to go that way. Notice also that in XML-compliant HTML you need to write single attributes like selected and checked as selected="selected" and checked="checked", respectively.

274

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

The form features fieldsets for grouping elements into logical units and labels for connecting explanation texts with specific form elements. This helps a lot with form accessibility, as it provides logical organization and avoids ambiguity.

Basics of Forms with JavaScript

Reaching and altering forms in JavaScript can be achieved in several different ways. As always, there is the DOM scripting way of simply reaching the forms and their elements via getElementsByTagName() and getElementById(), but there is also an object called forms that contains all the forms in the current document.

This object allows you to reach the forms in the document in three different ways:

Via their index number where index is an integer, for example, document.forms[2] for the third form.

Via their name defined in the name attribute as an object, for example document.forms.myForm.

Via their name defined in the name attribute as a string, for example document.forms['myForm']. This is necessary when the name contains special characters or spaces, and you cannot notate it as an object.

Form Properties

The forms object itself has only one property, length, and it stores the number of forms in the document.

Each of the forms, however, has more properties you can use, all of which can be read and changed:

action: The script the form data is sent to when the form is submitted

encoding: The encoding of the form as defined in the enctype attribute of the FORM element

method: The submission method of the form—either POST or GET

name: The name of the form as defined in the name attribute (not in the id!)

target: The target the form data should be sent to (only important if you use frames or multiple windows)

Form Methods

The Form object has only two methods:

reset(): Resets the form to its initial state, which means that all the entries and choices the user made get undone, and the form shows the initial values defined in the value, selected, or checked attributes of the individual elements. Be aware of the difference— reset() does not clear the form, but restores it to its initial state. This is the same effect a user gets when he activates a Reset button in the form.

submit(): Submits the form.

C H A P T E R 7 J A V A S C R I P T A N D U S E R I N T E R A C T I O N : N A V I G A T I O N A N D F O R M S

275

Both methods simulate browser functionality—namely activating a Reset or a Submit but- ton—and you should make sure that you don’t use them to take away necessary interactivity from the user. There is a good reason that forms get submitted when the user clicks a Submit button or hits Enter on the keyboard—it is the most accessible way, and if you hijack this functionality and submit the form when users interact with other elements, you may force them to submit the form prematurely.

Form Elements

Every form in the forms collection has as a property an object called elements, which in essence is an array of all the form elements inside this form (as opposed to all HTML elements). You can reach the elements the same way you initially reach the forms, via the index number, the object name, or the name as a string in square brackets:

var elm = document.forms[0].elements[2];

var elm = document.forms.myForm.elements.myElement;

var elm = document.forms.myForm.elements['myElement'];

As you can see in the last example, you can mix and match the notations. You can also use variables instead of the index numbers or the strings inside the brackets.

The elements collection itself has one read-only property called length. You could, for example, use this property to loop through all elements in a form and read out their type:

var myForm = document.forms[0];

var formElements = myForm.elements; var all = formElements.length;

for( var i = 0; i < all; i++ ) { alert( formElements[i].type );

}

Each element inside the collection has several properties; which ones are supported is dependent on the type of the element. I’ll now list all the properties and list the elements that support this property in parentheses. We’ll go through the different elements in detail later on in this chapter:

checked: Boolean if the element was checked or not (buttons, check boxes, radio buttons)

defaultChecked: Boolean if the element was initially checked (check boxes, radio buttons)

value: The value of the element as defined in the value attribute (all elements but select boxes)

defaultValue: The initial value of the element (text boxes, text areas)

form: The form the element resides in (read only—all elements)

name: Name of the element (all elements)

type: The type of the element (read only—all elements)