Section IV.11. Combining Forces: A Custom Newsletter


IV.11. Combining Forces: A Custom Newsletter

To round out the discussion of dynamic content, I am going to present an application that demonstrates several aspects of dynamic content in action. Unfortunately, the Macintosh version of IE is missing some key ingredients to make this application run on that platform, so this only works on IE 5 and later for Windows and W3C DOM browsers that support basic operations of the Range object (Mozilla, Safari 1.3/2.0 or later, and Opera 8 or later). The example is a newsletter that adjusts its content based on the reader's filtering choices. For ease of demonstration, the newsletter arrives with a total of five stories (containing some real text and some gibberish to fill space) condensed into a single document (you could also code the page to retrieve the data as XML via the XMLHttpRequest object). A controller box in the upper right corner of the page allows the reader to filter the stories so that only those stories containing specified keywords appear on the page (see Figure IV-3). Not only does the application filter the stories, it orders them based on the number of matching keywords in the stories. In a real application of this type, you might store a profile of subject keywords on the client machine as a cookie and let the document automatically perform the filtering as it loads.

Figure IV-3. A newsletter that uses DHTML to customize its content


Each story arrives inside a div element of class wrapper; each story also has a unique ID that is essentially a serial number identifying the date of the story and its number among the stories of that day. Nested inside each div element are both an H3 element (class of headline) and one or more p elements (class of story). In Example IV-12, the style sheet definition includes placeholders for assigning style rules to each of those classes. At load time, all items of the wrapper class are hidden, so they are ignored by the rendering engine.

The controller box (ID of filter) with all the checkboxes is defined as an absolute-positioned element at the top right of the page. It's style sheet rule initially hides the controller so that only scriptable browsersthose that respond to the clicksdisplay it. The markup for the checkboxes does not include event handlers. Instead, event handlers are assigned by script during initialization.

The only other noteworthy element is a div element of ID myNews (just above the first story div element). This is an empty placeholder where stories will be inserted for viewing by the user.

A click of any of the checkboxes in the controller box triggers the searching and sorting of stories. Two global variables assist in searching and sorting. The keywords array is established at initialization time to store all the keywords from the checkboxes. The foundStories array is filled each time a new filtering task is requested. Each entry in the foundStories array is an object with two properties: id, which corresponds to the ID of a selected story, and weight, which is a numeric value that indicates how many times a keyword appears in that story.

Now skip to the filter( ) function, which is the primary function of this application. It is invoked at load time and by each click on a checkbox. The function's first task is to clear the myNews element by removing all child nodes if any are present. Then the function looks for each div element with a class name of wrapper, so that the div elements can be passed along to the searchAndWeigh( ) function. This is where a DOM-specific invocation of a text range object allows for extraction of just the text from the story. Because the W3C DOM text range doesn't offer the convenience of IE's findText( ) function, we use the old standby of the indexOf( ) function for the string value (regular expressions would also be suitable). By manipulating the start position of the indexOf( ) action inside a while loop, the function can count the number of matches for each keyword within the text.

For each chosen keyword match, the parent element of the current div (the element whose tags surround the matched text) is passed to the getdIVId( ) function. This function makes sure the parent element of the found item has a class associated with it (meaning that it is of the wrapper, headline, or story class). The goal is to find the wrapper class of the matched string, so geTDIVId( ) works its way up the chain of parent elements until it finds a wrapper element. Now it's time to add the story belonging to the wrapper class element to the array of found stories. But since the story may have been found during an earlier match, there is a check to see if it's already in the array. If so, the array entry's weight property is incremented by one. Otherwise, the new story is added to the foundStories array.

Since it is conceivable that no story may have a matched keyword (or no keywords are selected), a short routine loads the foundStories array with information from every story in the document. Thus, if there are no matches, the stories appear in the order in which they were entered into the document. Otherwise, the foundStories array is sorted by the weight property of each array entry.

The finale of Example IV-12 is at hand. With the foundStories array as a guide, the hidden div elements are cloned (to preserve the originals untouched). The className properties of the clones are set to a different class selector whose display style property allows the element to be displayed. Then each clone is appended to the end of the myNews element. As the last step, the foundStories array is emptied, so it is ready to do it all over again when the reader clicks on another checkbox.

Example IV-12. A custom newsletter filter that uses DHTML

 <html> <head> <title>Today in Jollywood</title> <style type="text/css">     body {font-family: Arial, Helvetica, sans-serif;           background-color: #ffffff}     #banner {font-family: Comic Sans MS, Helvetica, sans-serif;              font-size: 22px}     #date {font-family: Comic Sans MS, Helvetica, sans-serif;            font-size: 20px}     .wrapper {display: none}     .unwrapper {display: block}     .headline {}     .story {}     #filter {position: absolute; top: 10px; left: 330px; width: 400px;              border: solid red 3px; padding: 2px;              font-size: 12px; background-color: coral; display: none} </style> <script language="JavaScript" type="text/javascript"> function addEvent(elem, evtType, func, capture) {    capture = (capture) ? capture : false;    if (elem.addEventListener) {       elem.addEventListener(evtType, func, capture);    } else if (elem.attachEvent) {       elem.attachEvent("on" + evtType, func);    } } // Global variables and object constructor var keywords = new Array( ); var foundStories = new Array( ); function story(id, weight) {     this.id = id;     this.weight = weight; } // Find story's "wrapper" class and stuff into foundStories array // (or increment weight) function getDIVId(elem) {     if (!elem.className) {         return;     }     while (elem.className != "wrapper") {         elem = elem.parentNode;     }     if (elem.className != "wrapper") {         return;     }     for (var i = 0; i < foundStories.length; i++) {         if (foundStories[i].id == elem.id) {             foundStories[i].weight++;             return;         }     }     foundStories[foundStories.length] = new story(elem.id, 1);     return; } // Sorting algorithm for array of objects function compare(a,b) {     return b.weight - a.weight; } // Look for keyword match(es) in a div's text range function searchAndWeigh(div) {     var txtRange, txt, start;     var isW3C = (typeof Range != "undefined") ? true : false;     var isIE = (document.body.createTextRange) ? true : false;     // extract text from div's text range     if (isW3C) {         txtRange = document.createRange( );         txtRange.selectNode(div);         txt = txtRange.toString( );     } else if (isIE) {         txtRange = document.body.createTextRange( );         txtRange.moveToElementText(div);         txt = txtRange.text;     } else {         return;     }     // search text for matches     for (var i = 0; i < keywords.length; i++) {         // But only for checkmarked keywords         if (document.filterer.elements[i].checked) {             start = 0;             // use indexOf( ), advancing start index as needed             while (txt.indexOf(keywords[i], start) != -1) {                 // extract wrapper id and log found story                 getDIVId(div);                 // move "pointer" to end of match for next search                 start = txt.indexOf(keywords[i], start) + keywords[i].length;             }         }     } } // Main function finds matches and displays stories function filter(evt) {     var divs, i;     var news = document.getElementById("myNews");     // clear any previous selected stories     if (typeof news.childNodes == "undefined") {return;}     while (news.hasChildNodes( )) {         news.removeChild(news.firstChild);     }     // look for keyword matches     divs = document.getElementsByTagName("div");     for (i = 0; i < divs.length; i++) {         if (divs[i].className && divs[i].className == "wrapper") {             searchAndWeigh(divs[i]);         }     }     if (foundStories.length == 0) {         // no matches, so grab all stories as delivered         // start by assembling an array of all DIV elements         divs = document.getElementsByTagName("div");         for (i = 0; i < divs.length; i++) {             if (divs[i].className && divs[i].className == "wrapper") {                 foundStories[foundStories.length] = new story(divs[i].id);             }         }     } else {         // sort selected stories by weight         foundStories.sort(compare);     }     var oneStory = "";     for (i = 0; i < foundStories.length; i++) {         oneStory = document.getElementById(foundStories[i].id).cloneNode(true);         oneStory.className = "unwrapper";         document.getElementById("myNews").appendChild(oneStory);     }     foundStories.length = 0; } // Initialize for scriptable access function init( ) {     var form = document.filterer;     for (var i = 0; i < form.elements.length; i++) {         addEvent(form.elements[i], "click", filter);         keywords[i] = form.elements[i].value;     }     document.getElementById("filter").style.display = "block";     filter( ); } addEvent(window, "load", init); </script> </head> <body"> <h1 >Today in Jollywood</h1> <h2 >Tuesday, April 1, 2007</h2> <hr> <div > </div> <div  > <h3 >Robin Williams Begins New Epic</h3> <p >Perennial funny man and Oscar-winner, Robin WIlliams has begun location shooting on a new film based on an epic story. Sally ("Blurbs") Thorgenson of KACL radio, who praised "RV" as "the best film of 2006," has already supplied the review excerpt for the next film's advertising campaign: "Perhaps the best film of the new millennium!" says Thorgenson, talk-show host and past president of the Seattle chapter of the Robin Williams Fan Club. The Innscouldn't it trumple from rathe night she signs. Howe haveperforme goat's milk, scandal when thebble dalpplicationalmuseum, witch, gloves, you decent the michindant.</p> </div> <div  > <h3 >Critic's Poll Looking Bleak</h3> <p >A recent poll of the top film critics shows a preference for foreign films this year. "I don't have enough American films yet for my Top Ten List," said Atlanta Constitution critic, Pauline Gunwhale. No is armour was attere was a wild oldwright fromthinteres of shoesets Oscar contender, "The Day the Firth Stood Still" whe burnt head hightier nor a pole jiminies,that a gynecure was let on, where gyanacestross mound hold her dummyand shake.</p> </div> <div  > <h3 >Summer Blockbuster Wrap-Up</h3> <p >Despite a world-wide boycott from some religious groups, the animated film "Brokeback Batman" won the hearts and dollars of movie-goers this summer. Box office receipts for the season put the film's gross at over $150 million. Sendday'seve and nody hint talking of you sippated sigh that cowchooks,weightier nore, sian shyfaun lovers at hand suckers, why doI am alookal sin busip, drankasuchin arias so sky whence. </p> </div> <div  > <h3 >Shakespeare in Simpson's Future?</h3> <p >Undaunted by lackluster box-office results from last Christmas' "Dukes of Hazard on Ice," Jessica Simpson has been auditioning for Broadway's top Shakespearean theater directors. "No more hot pants for me," the artist was seen lip-synching between auditions, "From now on, it will just be good forsoothing and stuff like that." He crumblin if so be somegoat's milk sense. Really? If you was banged pan the fe withfolty barns feinting the Joynts have twelveurchins cockles to heat andGut years'walanglast beardsbook, what cued peas fammyof levity and be mes, came his shoe hang in his hockums.</p> </div> <div  > <h3 >Stewart to Appear in Sequel</h3> <p >Although he jokes about his movie career regularly, fake TV news anchor Jon Stewart will reprise his role in "Death to Smoochie II," coming to theaters this Christmas. Critics hailed the New Jersey comic's last outing as the "non-event of the season." This the way thing,what seven wrothscoffing bedouee lipoleums. Kiss this mand shoos arouna peck of night, in sum ear of old Willingdone. Thejinnies and scampull's syrup.</p> </div> <hr> <p >Copyright 2007 Jollywood Blabber, Inc. All Rights Reserved.</p> <div > <p>Filter news by the following keyword(s):</p> <form name="filterer"> <p><input type="checkbox" value="director">director <input type="checkbox" value="box">box (office) <input type="checkbox" value="critic">critic <input type="checkbox" value="summer">summer <input type="checkbox" value="Christmas">Christmas</p> </form> </div> </body> </html> 

Some people might argue that it is a waste of bandwidth to download content that the viewer may not need. Indeed, if you have a server program capable of searching and sorting, you could use the XMLHttpRequest object to obtain data for stories fairly quickly in response to the checkbox clicks (but not as fast as doing all of the action on the client). When you want to give the user quick access to changeable content, a brief initial delay in downloading the complete content is preferable to individual delays later in the process.

Example IV-12 demonstrates that even when IE has its own way of doing things (as in its Textrange object), you can combine the proprietary DOM with W3C DOM syntax that it does support (as with the cloneNode( ) and appendNode( ) methods). This makes it easier to implement applications that change document content in both DOMs.




Dynamic HTML. The Definitive Reference
Dynamic HTML: The Definitive Reference
ISBN: 0596527403
EAN: 2147483647
Year: 2004
Pages: 120
Authors: Danny Goodman

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net