15.1. The Client Components

The client-side components of an Ajax solution are in charge of communicating with the server and displaying the received data to the user. For FooReader.NET, several client-side components are necessary to manage the overall user experience.

  • The user interface: The UI ties the user to his or her data. Because the UI is essentially a web page, the usual suspects of web browser technologies are used. The design is marked up in HTML, and CSS (and a little bit of JavaScript) styles it for the desired look and feel.

  • XParser: The JavaScript library responsible for requesting and parsing feeds.

  • The JavaScript code: Drives the UI, taking the information XParser received and displaying it to the user. This is contained in the fooreader.js file.

15.1.1. The User Interface

The key to any successful application is the design of the user interface. If the user cannot use the application, there is no reason for the application to exist. FooReader.NET was designed for ease of use. In fact, it borrows heavily from the Microsoft Outlook 2003 (and later) user interface. It has a three-pane interface. The first two panes are fixed width, while the third pane is fluid. (See Figure 15-1.)

Figure 15.1. Figure 15-1

The interface is contained in default.htm, and its layout is as follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xml:lang="en" lang="en"  xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>FooReader.NET (Version 1.5)</title>
    <link rel="stylesheet" type="text/css" href="css/FooReader.css" />
    <script type="text/javascript" src="js/zxml.src.js"></script>
    <script type="text/javascript" src="js/XParser.js"></script>
    <script type="text/javascript" src="js/FooReader.js"></script>
</head>
<body>
    <div id="divLoading">
        <img src="img/progress.gif" alt="Loading" />
    </div>

    <div id="divTopBar">
        <img src="img/top_logo.gif" alt="FooReader.NET" />
        <div id="divLicense">
            <a href="http://creativecommons.org/licenses/by-nc-sa/2.5/"
                title="Some Rights Reserved" target="_blank">License</a>
        </div>
    </div>

    <div id="divPaneContainer">
        <div id="divFeedsPane">
            <div class="paneheader">Feeds</div>
            <div id="divFeedList"></div>
        </div>

        <div id="divItemsPane">
            <div id="divViewingItem" class="paneheader">Items</div>
            <div id="divItemList"></div>
        </div>

        <div id="divReadingPane">
            <div id="divMessageContainer">
                <div id="divMessageHeader">
                    <div id="divMessageTitle"></div>
                    <a href="" id="aMessageLink" title="Click to goto posting."
                        target="_new">Travel to Post</a>
                </div>
                <div id="divMessageBody"></div>
            </div>
        </div>
    </div>
</body>
</html>

The first two direct children of the document's body are the divLoading and divTopBar elements. The former provides a UI cue, telling the user that the application is loading a feed. It appears when a request is made to the server application and hides when the server's response is received and processed. The divTopBar element primarily serves as a title bar in conventional applications; it tells the user that the application they're using is FooReader.NET, and it provides a link to the Creative Commons license that it is released under.

Next is the divPaneContainer element. As its name suggests, the three panes reside in this element. This element allows certain styles to easily be applied to the group of panes, as opposed to applying them to each pane separately.

The first pane, called the feeds pane, displays the different feeds as links that the user can click on. A <div/> element with an id of divFeedList in the feeds pane allows the feeds list to be dynamically written to the document. Feeds are block level <a/> elements with a CSS class of "feedlink". The following is the HTML of these links.

<a href="[url of feed]" class="feedlink" title="Load [feed title]">Feed title</a>

When the user clicks on a feed, the feed's items are loaded into the second pane: the items pane. This pane has two elements that are used to display information.

  • The first is a <div/> element with an id of divViewingItem (it is also the pane's header). This element displays which feed is currently loaded into the application.

  • The second element is another <div/> element, whose id attribute is set to divItemList. This element will contain a list of <item/> RSS elements or <entry/> Atom elements, and they are dynamically added to divItemList.

The HTML structure for these items is as follows.

<a class="itemlink" href="[item url]" frfeeditem="[item number]" id="item[number]">
    <div class="itemheadline">[Headline]</div>
    <div class="itemdate">[Date]</div>
</a>

This HTML is fairly standard, except for the frfeeditem attribute in the <a/> element. When a feed is loaded and the items are added to the page, each item is assigned a number by which to identify itself.

When the user clicks an item, it loads the item into the last pane: the reading pane. This pane has three elements that display the item's information. The first, whose id is divMessageTitle, is where the <title/> element of RSS and Atom feeds is displayed. The second element has an id of aMessageLink whose href attribute is changed dynamically. Finally, the last element is divMessageBody, where the contents of the <rss:description/> and <atom:content/> elements are displayed.

This page requires one style sheet, FooReader.css, along with three JavaScript files: the zXml library, XParser, and FooReader.js, which contains all the client functionality.

Since FooReader.NET uses XParser, the application will not work in Safari. It does, however, work in IE 6+, Firefox 1+, and Opera 9+.

The finished UI is achieved by a combination of CSS and JavaScript; the CSS sets the size of the document's body (a necessity for Opera), the three panes, and styles the other elements. JavaScript sizes the elements inside the panes.

15.1.2. Styling the Interface

The only style sheet used in FooReader.NET is FooReader.css, and it exists in the css directory. One of the keys to making this interface work is the size of the browser's viewport. The page is explicitly set to use all available vertical space in the browser's window.

html, body {
    height: 100%;
    margin: 0px;
    overflow: hidden;
    background-color: gray;
    font: 11px verdana, arial, helvetica, sans-serif;
}

The height property is set to 100%. This property is necessary for Opera; otherwise, the JavaScript portion of styling the elements would have to branch code to accommodate the differing Browser Object Models. The overflow is set to hidden, because some of the elements in the page will cause scrollbars to appear in the browser's window. The goal of the UI is to make FooReader.NET feel like a normal application, and scrollbars at the document level inhibit that feeling (the individual panes will scroll, if necessary).

15.1.2.1. The Topbar

The next rules are for divTopBar, and the elements contained in it:

/* The Topbar */
#divTopBar {
    background: gray url("../img/top_bg.gif") repeat-x;
    height: 31px;
    padding-left: 25px;
    position: relative;
}

The top bar has a background color of gray (to match the gray background of the page) and a background image of top_bg.gif. Its left side is padded by 25 pixels. This pushes the <img/> element to the right. Its relative position allows for any child elements to be positioned within the confines of the element, like the following divLicense element:

#divLicense {
    position: absolute;
    right: 10px;
    top: 3px;
}

#divLicense a {
    color: white;
    padding: 1px 4px 2px 4px;
}

#divLicense a:hover {
    background: blue url("../img/toolbar_back.gif") repeat-x;
    border: 1px solid #000080;
    color: #000080;
    padding: 0px 3px 1px 3px;
}

This element contains one link: the license. It is absolutely positioned 10 pixels from the right edge and 3 pixels from the top of divTopBar. The link inside divLicense has a text color of white. When the user moves their mouse pointer over the link, it gains a background image that repeats horizontally, a blue border 1 pixel in width, the text color changes to a dark blue, and the padding is adjusted to be 1 pixel less on all sides. This change in padding occurs because a border is added to the element. If no adjustment was made to the padding, the link would actually grow 1 pixel on each side.

15.1.2.2. The Loading Cue

In the HTML structure, the divLoading element contains an image. This image shows an animated progress bar; it's about 5 to 10 pixels high and a little under 300 pixels in length.

The image's parent element, the <div/> element, is absolutely positioned, and its display property is set to none, removing it completely from the document flow.

/* The loading <div/> */
#divLoading {
    position: absolute;
    display: none;
    top: 20%;
    left: 35%;
    width: 302px;
    z-index: 5;
    background: transparent url("../img/loading.gif") no-repeat;
    padding: 30px 10px;
}

Probably the most important style declaration is z-index. Since the element is first in the HTML document, it is hidden by the other elements in the page. Specifying a z-index greater than 0 causes the loading <div/> element to be placed in front of the other elements, making it visible to the user (see Figure 15-2).

15.1.2.3. The Panes

The three panes are contained within the aptly named divPaneContainer element. This element positions the panes in their desired location: away from the browser's left edge and the top bar:

#divPaneContainer {
    position: relative;
    top: 10px;
    left: 10px;
}

Positioning this element allows freedom from the tedium of positioning all the panes individually.

Figure 15.2. Figure 15-2

The feeds and items pane also have a header at the top of each pane. This header lets the user know what type of data the two panes contain. They are 20 pixels in height and have bold, white text. Here is its style rule:

.paneheader {
    height: 20px;
    background-image: url("../img/header_background.gif");
    font: bold 16px arial;
    color: white;
    padding: 2px 0px 2px 5px;
    letter-spacing: 1px;
    overflow: hidden;
}

15.1.2.3.1. The Feeds Pane

The first pane is the feed pane. The divFeedsPane element itself is simple, as the following rule shows:

#divFeedsPane {
    float: left;
    width: 148px;
    border: 1px solid navy;
    background-color: white;
    overflow: hidden;
}

The element is set to float left and is 148 pixels wide. Floating an element shifts the element to the right or left. In this case, divFeedsPane is shifted to the left. This makes the items pane flow along its right side, even though they are both block-level elements. To ensure that the pane always maintains it size, its overflow property is set to hidden. This hides any content that extends beyond the pane's boundaries. It is desirable, however, for the pane's contents to scroll. That is part of divFeedList's job, and here is its CSS rule:

#divFeedList {
    padding: 5px 1px 5px 1px;
    overflow: auto;
}

This element contains the feed links. Its overflow property is set to auto. This allows divFeedList to scroll if its contents make the element larger than divFeedsPane. The scrollbars will appear inside divFeedsPane, making the contents scrollable, while keeping the pane itself the same (see Figure 15-3).

Figure 15.3. Figure 15-3

The links in this pane are styled as block-level elements. They have padding to give them visual separation from each other.

a.feedlink {
    display: block;
    padding: 5px;
    font: bold 12px arial;
    text-decoration: none;
    color: #5583d3;
}

a.feedlink:hover {
    color: #3768B9;
    text-decoration: underline;
}

15.1.2.3.2. The Items Pane

The items pane's CSS rule closely resembles that of the feed's pane:

#divItemsPane {
    float: left;
    width: 225px;
    border: 1px solid navy;
    background-color: white;
    margin-left: 5px;
    margin-right: 5px !important;
    margin-right: 2px;
    overflow: hidden;
}

It, too, floats left and hides its overflow. The right and left margins add space between this pane and the other two panes; however, IE6 and the other browsers render margins differently. In order for every browser to render the UI the same, the !important declarative must be used. The first margin-right uses this. So IE7, Firefox, and Opera will use that specific value regardless of what margin-right is assigned in the next declaration. The second assignment is for IE6. It adds an extra line to the CSS, but it makes the UI look uniform in all four browsers.

The items in this pane also have their own styling. If you refer back to the item HTML structure mentioned earlier, you see that they are simply <a/> elements with a CSS class of itemlink. These items have two states. The first is their normal state; how they look when the user hasn't clicked one. When a user clicks an item, it the JavaScript code changes the CSS class to itemlink-selected. Both states share many similar style declarations.

a.itemlink, a.itemlink-selected {
    border-bottom: 1px solid #EAE9E1;
    background-image: url("../img/item_icon.gif");
    background-repeat: no-repeat;
    cursor: pointer;
    text-decoration: none;
    display: block;
    padding: 2px;
    font: 11px tahoma;
}

The remaining rules for the items are to differentiate the two states from each other, as well as define the :hover pseudo-classes for the normal state:

a.itemlink {
    background-color: white;
    color: #808080;
}

a.itemlink:hover {
    background-color: #D3E5FA;
}

a.itemlink:hover .itemheadline {
    color: black;

}

.itemheadline,.itemdate {
    margin-left: 20px;
}

a.itemlink-selected {
    background-color: #316AC5;
    color: white;
}

15.1.2.3.3. The Reading Pane

The third pane is different from the other panes in that it has no defined width, as the following CSS shows:

#divReadingPane {
    margin: 0px 20px 0px 0px;
    border: 1px solid black;
    background-color: white;
    height: 100%;
    overflow: hidden;
}

Instead, the browser automatically sizes the element's width to fill the remainder of divPaneContainer, and the margin declaration in this CSS rule brings the right edge in by 20 pixels. Like the two previous panes, the reading pane's overflow is set to hidden. Unlike the other panes, however, the height is specified to 100%. This makes the pane's height that of the pane container.

The direct child of divReadingPane is divMessageContainer, which contains the message elements. Other than serving this purpose, it pads the message area by 5 pixels on all sides:

#divMessageContainer {
    padding: 5px;
}

The first part of a message is the header, which contains the article's title and a link to take the user to the article. Its CSS follows:

#divMessageHeader {
    height: 34px;
    background-color: white;
    border-bottom: 1px solid #ACA899;
    padding: 8px;
}

#divMessageTitle {
    font: bold 16px arial;
}

#aMessageLink {
    font: 11px arial;
}

The divMessageBody element is where the feed item's content is displayed, and the following CSS rule applies to this element.

#divMessageBody {
    background-color: white;
    padding: 0px 0px 0px 5px;
    font: 13px tahoma;
    overflow: auto;
}

This portion of the reading pane scrolls if the contents exceed the height of the reading pane. Its height is not specified.

The height of the elements that contain content (divFeedList, divItemList, and divMessageBody) are not styled through CSS. Instead, that is handled by JavaScript.

15.1.3. Driving the UI

The JavaScript contained in fooreader.js controls all aspects of the UI. It retrieves the feed list, parses it, and populates the feeds pane. It creates XParser objects to request, receive, and parse RSS and Atom feeds, and uses that information to populate the items and reading panes. It sizes many elements of the UI and resizes them when the window's size changes. In short, it's the backbone of the client-side components.

15.1.3.1. The Helper Functions

The majority of code is contained inside the fooReader object, but two functions stand alone to aid in the element's resizing. The first function is called getStyle(), and it is a cross-browser approach to getting the value of a specific style property. It accepts two arguments, the element and the CSS property name.

function getStyle(oElement, sProperty) {
    var sStyle;

    if (typeof window.getComputedStyle == "undefined") {
        sStyle = oElement.currentStyle[sProperty];
    } else {
        sStyle = getComputedStyle(oElement, "")[sProperty];
    }

    return sStyle;
}

This code uses IE's currentStyle property and the W3C DOM getComputedStyle() method to retrieve the value of a specific property.

The second function, getStyleNumber(), performs a similar activity, except that it returns an integer instead of a string:

function getStyleNumber(oElement, sProperty) {
    return parseInt(getStyle(oElement, sProperty));
}

15.1.3.2. The fooReader Object

As mentioned earlier, the fooReader object contains most of the JavaScript code, making it the main part of the application. It contains various properties and methods necessary to run the UI. It is the only object of its kind in the application. Therefore, it is defined in object literal notation:

var fooReader = {
    parser  : null,
    feeds   : [],

    //HTML elements
    divFeedList     : null,
    divViewingItem  : null,
    divItemList     : null,
    divMessageTitle : null,
    aMessageLink    : null,
    divMessageBody  : null,
    divLoading      : null,

    selectedItem    : null,

    //more code here
}

//more code here

The properties that comprise this definition are as follows:

  • The first property, parser, contains an XParser feed object.

  • Next is an array called feeds, and this contains a list of feeds retrieved from the feeds list.

  • The next seven properties reference HTMLElement objects. These elements are constantly used throughout the application's session, so it makes good sense to cache them.

  • The last property, selectedItem, is a pointer to the item (<a/> element) last clicked by the user.

These properties are initialized as null to prevent any errors from occurring.

15.1.3.3. Initializing the UI

Before the user can interact with the UI, the HTMLElement properties need to be initialized. The method for this is called init(), and aside from assigning the properties for the elements, it sizes the UI elements that need dynamic sizes. This method is called only on the load and resize events of the window. Therefore, the function exists as a method of the fooReader object, but its definition lies outside of the main object definition. This doesn't really do anything except present the visual differentiation between this method and the other members of fooReader.

var fooReader = {
    parser  : null,
    feeds   : [],

    //HTML elements

divFeedList     : null,
    divViewingItem  : null,
    divItemList     : null,
    divMessageTitle : null,
    aMessageLink    : null,
    divMessageBody  : null,
    divLoading      : null,

    selectedItem    : null,

    //more code here
}

fooReader.init = function (evt) {
    evt = evt || window.event;

    if (evt.type == "load") { //Things to initialize only on the load event
        fooReader.divFeedList        = document.getElementById("divFeedList");
        fooReader.divViewingItem     = document.getElementById("divViewingItem");
        fooReader.divItemList        = document.getElementById("divItemList");
        fooReader.divMessageTitle    = document.getElementById("divMessageTitle");
        fooReader.aMessageLink       = document.getElementById("aMessageLink");
        fooReader.divMessageBody     = document.getElementById("divMessageBody");
        fooReader.divLoading         = document.getElementById("divLoading");

        //more code here
    }

    var divPaneContainer = document.getElementById("divPaneContainer");
    var divReadingPane = document.getElementById("divReadingPane");
    var divMessageContainer = document.getElementById("divMessageContainer");
    var divMessageHeader = document.getElementById("divMessageHeader");

    //more code here

};

window.onload   = fooReader.init;
window.onresize = fooReader.init;

Since developers still have to cope with differing event models, the first line of the method retrieves the correct event object. Next, the event's type is checked to determine whether it was the load event that fired. If this is true, the various HTMLElement properties are assigned with the document.getElementById() method.

Outside the if block, other HTMLElements are retrieved and assigned to variables. These variables are used in the sizing operations that follow:

//fooReader object code here

fooReader.init = function (evt) {
    evt = evt || window.event;

    if (evt.type == "load") { //Things to initialize only on the load event
        fooReader.divFeedList        = document.getElementById("divFeedList");

fooReader.divViewingItem     = document.getElementById("divViewingItem");
        fooReader.divItemList        = document.getElementById("divItemList");
        fooReader.divMessageTitle    = document.getElementById("divMessageTitle");
        fooReader.aMessageLink       = document.getElementById("aMessageLink");
        fooReader.divMessageBody     = document.getElementById("divMessageBody");
        fooReader.divLoading         = document.getElementById("divLoading");

        //more code here
    }

    var divPaneContainer = document.getElementById("divPaneContainer");
    var divReadingPane = document.getElementById("divReadingPane");
    var divMessageContainer = document.getElementById("divMessageContainer");
    var divMessageHeader = document.getElementById("divMessageHeader");


    var iDocHeight = document.documentElement.clientHeight;
    divPaneContainer.style.height = iDocHeight –
                                    divPaneContainer.offsetTop - 12 + "px";

    var iFeedsListHeight = divPaneContainer.offsetHeight -
                           fooReader.divViewingItem.offsetHeight -
                           getStyleNumber(fooReader.divFeedList, "paddingTop") -
                           getStyleNumber(fooReader.divFeedList, "paddingBottom");


    fooReader.divFeedList.style.height = iFeedsListHeight +  "px";

    //more code here
};

window.onload   = fooReader.init;
window.onresize = fooReader.init;

This new code begins by getting the height of the viewport with document.documentElement.clientHeight. That value is then used in conjunction with divPaneContainer's offsetTop to set divPaneContainer's height. The numeric constant, 12, is for visual purposes only, because it provides 12 pixels of space between the bottom of the container and the bottom of the window.

Next is the assignment of the iFeedsListHeight variable, which is used to set the height of divFeedList. This element's height is set to fill all available space in the pane. So, the calculation takes the size of the pane container's height, subtracts the size of the pane header by using the divViewingItem's offsetHeight property, and finally subtracts the paddingTop and paddingBottom style values from divFeedList itself. The two latter values both contribute to divFeedList's height. Therefore, they need to be included in the calculation. This size in combination with overflow: auto in the CSS makes the contents of this pane scroll if it exceeds the size of the pane.

Next, use the same process for the items pane, except substitute fooReader.divFeedList for fooReader.divItemList, like this:

//fooReader object code here

fooReader.init = function (evt) {
    evt = evt || window.event;

if (evt.type == "load") { //Things to initialize only on the load event
        fooReader.divFeedList        = document.getElementById("divFeedList");
        fooReader.divViewingItem     = document.getElementById("divViewingItem");
        fooReader.divItemList        = document.getElementById("divItemList");
        fooReader.divMessageTitle    = document.getElementById("divMessageTitle");
        fooReader.aMessageLink       = document.getElementById("aMessageLink");
        fooReader.divMessageBody     = document.getElementById("divMessageBody");
        fooReader.divLoading         = document.getElementById("divLoading");

        //more code here
    }

    var divPaneContainer = document.getElementById("divPaneContainer");
    var divReadingPane = document.getElementById("divReadingPane");
    var divMessageContainer = document.getElementById("divMessageContainer");
    var divMessageHeader = document.getElementById("divMessageHeader");

    var iDocHeight = document.documentElement.clientHeight;
    divPaneContainer.style.height = iDocHeight -
                                    divPaneContainer.offsetTop - 12 + "px";

    var iFeedsListHeight = divPaneContainer.offsetHeight -
                           fooReader.divViewingItem.offsetHeight -
                           getStyleNumber(fooReader.divFeedList, "paddingTop") -
                           getStyleNumber(fooReader.divFeedList, "paddingBottom");


    fooReader.divFeedList.style.height = iFeedsListHeight +  "px";


    var iItemListHeight = divPaneContainer.offsetHeight -
                          fooReader.divViewingItem.offsetHeight -
                          getStyleNumber(fooReader.divItemList, "paddingTop") -
                          getStyleNumber(fooReader.divItemList, "paddingBottom");

    fooReader.divItemList.style.height = iItemListHeight  + "px";


    var iMessageBodyHeight = divReadingPane.offsetHeight -
                             divMessageHeader.offsetHeight -
                             getStyleNumber(divMessageContainer, "paddingTop") -
                             getStyleNumber(divMessageContainer, "paddingTop");

    fooReader.divMessageBody.style.height = iMessageBodyHeight + "px";
};

window.onload   = fooReader.init;
window.onresize = fooReader.init;

Setting the height of divMessageBody follows somewhat the same pattern you've just seen. It uses the reading pane's and the message header's height instead of the pane container and header. It also gets the vertical padding values from the divMessageContainer element as opposed of the divMessageBody. The end result is the same, however. When the message body's height is set, the content will scroll if necessary.

15.1.3.4. Showing and Hiding Loading Cues

The fooReader object exposes two methods for showing and hiding the loading cue: showLoadingDiv() and hideLoadingDiv().

hideLoadingDiv : function () {
        this.divLoading.style.display = "none";
},

showLoadingDiv : function () {
    this.divLoading.style.display = "block";
},

These methods simply change the display property to "none" or "block" to hide and show the element.

15.1.3.5. Setting the Reading Pane's Content

There is one method that sets the content in the reading pane, and it is called setMessage(). This method adds content to the divMessageTitle, aMessageLink, and divMessageBody elements. It accepts three arguments, the message title, the link associated with the message, and the message's body, and it uses the values accordingly.

setMessage : function (sTitle, sHref, sMessageBody) {
    this.divMessageTitle.innerHTML = sTitle;
    this.aMessageLink.href = sHref;
    this.divMessageBody.innerHTML = sMessageBody;
},

15.1.3.6. Item Methods

There are four methods associated with the items pane, and they are responsible for populating the item pane with items, changing the item pane header, clearing the item pane, and selecting an item programmatically.

15.1.3.6.1. Adding Items

The first method, addItem(), dynamically creates an item's HTML and appends it to the item pane. It accepts two arguments: an XParser item object and the number associated with the item.

addItem : function (oItem, iNum) {
    var aItem = document.createElement("A");
    aItem.className = "itemlink";
    aItem.href = oItem.link.value;

    aItem.setAttribute("frFeedItem",iNum);
    aItem.id = "item" + iNum;

    var divHeadline = document.createElement("DIV");
    divHeadline.className = "itemheadline";
    divHeadline.innerHTML = oItem.title.value;

    var divDate = document.createElement("DIV");
    divDate.className = "itemdate";

divDate.appendChild(document.createTextNode("Date: " + oItem.date.value));
    aItem.appendChild(divHeadline);
    aItem.appendChild(divDate);

    //more code here

    this.divItemList.appendChild(aItem);
},

This code uses the information contained in the XParser item object to create the item's HTML. Notice that an attribute called "frFeedItem" is created for the aItem element. This attribute is used to contain the number associated with this item and is used later to add content to the reading pane.

At this point, clicking the item does nothing for the application. In fact, it takes the user to the URL specified in aItem's href property. This is not the desired functionality, so the click event must be handled. Clicking the item should do two things.

  • First, the currently selected item should return to its normal state, and the newly clicked item should become selected.

  • Second, the reading pane should be populated with content.

The onclick event handler executes in the scope of the <a/> element. Therefore, the code needs to use fooReader's API to access parts of the UI.

addItem : function (oItem, iNum) {
    var aItem = document.createElement("A");
    aItem.className = "itemlink";
    aItem.href = oItem.link.value;


    aItem.onclick = function () {
        var oSelectedItem = fooReader.selectedItem;

        if (oSelectedItem != this) {
            if (oSelectedItem) {
                oSelectedItem.className = "itemlink";
            }

            fooReader.selectedItem = this;
            this.className = "itemlink-selected";
        }

        var iItemNum = this.getAttribute("frFeedItem");

        var oItem = fooReader.parser.items[iItemNum];
        fooReader.setMessage(oItem.title.value,
                             oItem.link.value,
                             oItem.description.value);
        return false;
    };

    //more code here

aItem.setAttribute("frFeedItem",iNum);
    aItem.id = "item" + iNum;

    var divHeadline = document.createElement("DIV");
    divHeadline.className = "itemheadline";
    divHeadline.innerHTML = oItem.title.value;

    var divDate = document.createElement("DIV");
    divDate.className = "itemdate";
    divDate.appendChild(document.createTextNode("Date: " + oItem.date.value));
    aItem.appendChild(divHeadline);
    aItem.appendChild(divDate);

    this.divItemList.appendChild(aItem);
},

The first few lines of this code retrieve fooReader.selectedItem and determine whether or not this is a new item being clicked. If it is, then the old selected item's className property is set to "itemlink" to return it to the normal state. Then fooReader.selectedItem stores the new selected item and changes its className to "itemlink-selected".

Next, the value contained in the link's frFeedItem attribute is retrieved and used in the fooReader.parser.items collection to retrieve the correct item, and its information is sent to the setMessage() method. Finally, the event handler returns false, forcing the browser not to navigate to the URL specified by the href property.

The items now populate the items pane and perform the desired function when clicked. However, it would add a nice touch to do something when the item is double-clicked. In Outlook 2003+, double-clicking an item pulls up the e-mail message in a new window. FooReader.NET can essentially do the same thing; it can open a new window and navigate to the article's URL.

addItem : function (oItem, iNum) {
    var aItem = document.createElement("A");
    aItem.className = "itemlink";
    aItem.href = oItem.link.value;

    aItem.onclick = function () {
        var oSelectedItem = fooReader.selectedItem;

        if (oSelectedItem != this) {
            if (oSelectedItem) {
                oSelectedItem.className = "itemlink";
            }

            fooReader.selectedItem = this;
            this.className = "itemlink-selected";
        }

        var iItemNum = this.getAttribute("frFeedItem");

        var oItem = fooReader.parser.items[iItemNum];
        fooReader.setMessage(oItem.title.value,
                             oItem.link.value,
                             oItem.description.value);

return false;
    };


    aItem.ondblclick = function () {
        window.open(this.href);
    };

    aItem.setAttribute("frFeedItem",iNum);
    aItem.id = "item" + iNum;

    var divHeadline = document.createElement("DIV");
    divHeadline.className = "itemheadline";
    divHeadline.innerHTML = oItem.title.value;

    var divDate = document.createElement("DIV");
    divDate.className = "itemdate";
    divDate.appendChild(document.createTextNode("Date: " + oItem.date.value));
    aItem.appendChild(divHeadline);
    aItem.appendChild(divDate);

    this.divItemList.appendChild(aItem);
},

This code defines the ondbclick event handler, and like the onclick event handler, ondbclick executes within the context of the <a/> element. So, the this keyword references the HTMLAnchorElement. The body of the handler is simple: the window.open() method is called and the value of the href property is passed to it. The result is a new window opening to the new URL.

15.1.3.6.2. Changing the Heading Information

Letting the user know what feed is currently loaded is important, so a method called setViewingItem() is responsible for changing the item pane's heading to that of the feed's title. It accepts one argument, a string value containing the text to change to.

setViewingItem : function (sViewingItem) {
    this.divViewingItem.innerHTML = sViewingItem;
},

The divViewingItem element's CSS hides the element's overflow, so if the text is larger than the element's specified dimensions, it will not resize the element.

15.1.3.6.3. Clearing Items

Before loading a new feed into the application, the old feed's items must be removed from the items pane. The clearItems() method does this and accepts no arguments. It simply loops through and removes the child nodes of the divItemList element.

clearItems : function () {
    while (this.divItemList.hasChildNodes()) {
        this.divItemList.removeChild(this.divItemList.lastChild);
    }
},

When the loop exists, all items are removed from the pane, and it is ready to be populated again.

15.1.3.6.4. Selecting Items

There are times when selecting a specific item programmatically is necessary. For example, when a feed loads into the application, the first item is selected automatically. The selectItem() method does this, and the action simulates the effect of clicking an item. It accepts an integer argument, the number of the item to select.

selectItem      : function (iItemNum) {
    if (iItemNum > −1 && iItemNum < fooReader.parser.items.length) {
        var oItem = document.getElementById("item" + iItemNum);

        oItem.onclick.call(oItem);
    } else {
        throw new Error("FooReader Error: Supplied index is out of range.");
    }
},

This method first checks to see if iItemNum is within the range of 0 and the length of the fooReader.parser.items array. If so, then the code retrieves the item with document.getElementById(). Next, the item's onclick method is invoked in the scope of the item with the call() method. This doesn't actually raise the click event, but it simulates the action performed by a click in this application. Ideally, the DOM 2 dispatchEvent() method would be called, but IE does not support the method.

If the value of iItemNum is outside the specified bounds, then an error is thrown, stating that the value is out of range.

15.1.3.7. Feed Methods

There are a variety of methods involved with the feeds pane, and they load specific feeds, add feeds to the pane, and parse and retrieve the feed list.

15.1.3.7.1. Loading Specific Feeds

Loading a specific feed to be displayed into the UI involves the loadFeed() method. This method accepts one argument: the URL of the feed to retrieve. Its job is to show the loading cue and request the feed from the server by using XParser.

loadFeed : function (sUrl) {
    this.showLoadingDiv();

    var sUrl = "xmlproxy.aspx?feed=" + encodeURIComponent(sUrl);

    xparser.getFeed(sUrl, this.loadFeed_callBack, this);
},

The URL provided to the loadFeed() function is passed to the encodeURIComponent() JavaScript function to ensure proper transmission. Then by using XParser, the request is made to the server to retrieve the feed. When the request is completed, the loadFeed_callBack() method is called.

The callback method's purpose is to take the information from the XParser feed object, populate the items pane, and hide the loading cue.

loadFeed_callBack : function (oFeed) {
    this.parser = oFeed;

    this.clearItems();

    this.setViewingItem(this.parser.title.value);

    for (var i = 0, item; item = this.parser.items[i]; i++) {
        this.addItem(item, i);
    }

    this.hideLoadingDiv();

    this.selectItem(0);
},

The final step is selecting the first item in the items pane, which in turns loads data into the reading pane.

15.1.3.7.2. Adding Feeds

Adding feeds to the feeds pane is similar to adding items to the items pane. The method responsible for this is addFeed(), and it accepts two arguments: the feed's title and its URL.

addFeed : function (sTitle, sUrl) {
    var aFeedLink = document.createElement("a");
    aFeedLink.appendChild(document.createTextNode(sTitle));
    aFeedLink.href = sUrl;
    aFeedLink.className = "feedlink";
    aFeedLink.title = "Load " + sTitle;

    aFeedLink.onclick = function () {
        fooReader.loadFeed(this.href);
        return false;
    };

    this.divFeedList.appendChild(aFeedLink);
},

When the feed is clicked, the onclick event handler loads the selected feed with the loadFeed() method, which in turn populates the items pane and loads the first item into the reading pane.

15.1.3.7.3. The Feeds List

The list of feeds used by FooReader.NET is contained in the feeds.xml file. The XML in this file is in the Outline Processor Markup Language (OPML). OPML is an XML format developed by Dave Winer (who also developed RSS) for marking up outlines. It now has become the standard exchange format for feed lists in RSS and Atom aggregators.

OPML's structure is simple, and it looks like the following (and this is the list provided in the code download).

<opml version="1.0">
  <head>
    <title>FooReader Feed List</title>
  </head>
  <body>
    <outline text="Yahoo! Top Stories" title="Yahoo! Top Stories"
      type="rss" xmlUrl="http://rss.news.yahoo.com/rss/topstories"></outline>

    <outline text="Nicholas C. Zakas" title="Nicholas C. Zakas"
      type="rss" xmlUrl="http://www.nczonline.net/rss/"></outline>

    <outline text="Jeremy McPeak" title="Jeremy McPeak"
      type="rss" xmlUrl="http://www.wdonline.com/rss/"></outline>
  </body>
</opml>

The root element is <opml/>, and it must contain a version attribute. As of this writing, version 1.0 is the only version available. The root element contains two child elements: <head/> and <body/>.

The <head/> element is reminiscent of HTML's <head/> element in that it contains metadata. The previous example shows the <title/> element, and it represents the title of the document. Other elements valid in the <head/> are dateCreated, dateModified, ownerName, ownerEmail, expansionState, vertScrollState, windowTop, windowLeft, windowBottom, windowRight. The expansionState element contains a comma-separated list of line numbers that the aggregator should expand when displayed. The window elements define the position and size of the display window. An OPML processor may disregard any of these elements, but the most commonly used are title, dateCreated, and dateModified. The <body/> element contains the outline data, and it must contain at least one <outline/> element.

The <outline/> element represents one line of the outline. You can nest <outline/> elements with other <outline/> elements to create a hierarchical outline structure. The <outline/> element has a variety of attributes, but the most common are text, title, type, and xmlUrl.

  • The text and title attributes are used interchangeably, although it is recommended to use both and assign them the same value.

  • The type attribute contains the type of feed this line represents.

  • The xmlUrl attribute contains the link to the external feed.

While it is perfectly legal to nest <outline/> elements in the body, FooReader.NET recognizes only <outline/> elements that are direct children of <body/>.

Parsing the Feeds List

The fooReader object exposes a method and a class to parse the feeds list and organize the individual feeds.

The OpmlFileFeed class is a representation of a feed contained in feeds.xml. The constructor accepts one argument, the <outline/> node:

OpmlFileFeed : function (oFeedNode) {
    this.title = oFeedNode.getAttribute("title");
    this.url = oFeedNode.getAttribute("xmlUrl");
},

The class has two properties: title and url, which map to the title and xmlUrl attributes respectively. It is a simple class, and the instances of this class are assigned as elements of the fooReader.feeds[] array.

The function that reads feeds.xml is called readOpmlFile(). It accepts one argument: serialized XML data.

readOpmlFile : function (sXmlText) {
    var oXmlDom = zXmlDom.createDocument();
    oXmlDom.loadXML(sXmlText);

    var nlFeeds = zXPath.selectNodes(oXmlDom.documentElement, "body/outline");

    for (var i = 0, oFeed; oFeed = nlFeeds[i]; i++) {
        this.feeds.push( new this.OpmlFileFeed(oFeed) );
    }
},

This code creates an XML DOM and loads the XML data into it. Next, it uses an XPath evaluation to retrieve a NodeList of <outline/> elements and loops through them to populate the feeds[] array with OpmlFileFeed objects.

Retrieving the Feeds List

The last method of the application, and also the second to execute (after fooReader.init), is getFeedList(). This method creates an XHR request and requests the feeds.xml file from the server.

getFeedList : function () {
    var oHttp = zXmlHttp.createRequest();

    oHttp.onreadystatechange = function () {
        if (oHttp.readyState == 4) {
            if (oHttp.status == 200 || oHttp.status == 304) {
                //more code here
            }
        }
    };

    var date = (new Date()).getTime();
    oHttp.open("GET", "feeds.xml?time=" + date, true);
    oHttp.send(null);
}

It is important to note that the feeds.xml file is static. It is not dynamically created by the server application, and thus it is cached the first time it is requested. To get around this issue, an argument called time is added to the query string, and its value is set to the time of when getFeedList() executes. This ensures a unique request every time this method executes, foiling the caching attempts made by the browser.

Once the XHR receives the full response from the server, the application can begin to initialize.

getFeedList : function () {
    var oHttp = zXmlHttp.createRequest();

    oHttp.onreadystatechange = function () {
        if (oHttp.readyState == 4) {
            if (oHttp.status == 200 || oHttp.status == 304) {

                fooReader.readOpmlFile(oHttp.responseText);

                for (var i = 0, feed; feed = fooReader.feeds[i]; i++) {
                    fooReader.addFeed(feed.title, feed.url);
                }

                fooReader.loadFeed(fooReader.feeds[0].url);
            }
        }
    };

    var date = (new Date()).getTime();
    oHttp.open("GET", "feeds.xml?time=" + date, true);
    oHttp.send(null);
}

The first step is to parse the feeds list with the readOpmlFile() method. Once the feeds[] array is populated, the code then loops through that array and populates the feeds pane with addFeed(). The last new line uses the loadFeed() method to load the first feed in the list.

15.1.3.8. Finishing Up

All the code is in place and ready to execute, but nowhere in the code is getFeedsList() called. This is actually called in the init() method.

fooReader.init = function (evt) {
    evt = evt || window.event;

    if (evt.type == "load") { //Things to initialize only on the load event
        fooReader.divFeedList        = document.getElementById("divFeedList");
        fooReader.divViewingItem     = document.getElementById("divViewingItem");
        fooReader.divItemList        = document.getElementById("divItemList");
        fooReader.divMessageTitle    = document.getElementById("divMessageTitle");
        fooReader.aMessageLink       = document.getElementById("aMessageLink");
        fooReader.divMessageBody     = document.getElementById("divMessageBody");
        fooReader.divLoading         = document.getElementById("divLoading");

        fooReader.getFeedList();
    }

    var divPaneContainer = document.getElementById("divPaneContainer");
    var divReadingPane = document.getElementById("divReadingPane");

var divMessageContainer = document.getElementById("divMessageContainer");
    var divMessageHeader = document.getElementById("divMessageHeader");

    var iDocHeight = document.documentElement.clientHeight;
    divPaneContainer.style.height = iDocHeight –
                                    divPaneContainer.offsetTop - 12 + "px";

    var iFeedsListHeight = divPaneContainer.offsetHeight –
                           fooReader.divViewingItem.offsetHeight -
                           getStyleNumber(fooReader.divFeedList, "paddingTop") –
                           getStyleNumber(fooReader.divFeedList, "paddingBottom");


    fooReader.divFeedList.style.height = iFeedsListHeight +  "px";


    var iItemListHeight = divPaneContainer.offsetHeight -
                          fooReader.divViewingItem.offsetHeight -
                          getStyleNumber(fooReader.divItemList, "paddingTop") -
                          getStyleNumber(fooReader.divItemList, "paddingBottom");

    fooReader.divItemList.style.height = iItemListHeight  + "px";


    var iMessageBodyHeight = divReadingPane.offsetHeight -
                             divMessageHeader.offsetHeight -
                             getStyleNumber(divMessageContainer, "paddingTop") -
                             getStyleNumber(divMessageContainer, "paddingTop");

    fooReader.divMessageBody.style.height = iMessageBodyHeight + "px";
};

The getFeedsList() method is called only when the load event fires in the browser. Otherwise, it would execute each time the browser's window resizes, and it would interrupt the user's reading.

The code execution of the client-side application is a chain reaction. One method calls another method until the reading pane displays content. When the user clicks a feed or an item, the reaction begins from that point and continues until the reading pane's contents are updated.

The server-side application is different in that its sole purpose is to retrieve data from a remote server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset