7.4. Creating a News Ticker

Popular on both television news networks and web sites, a news ticker displays information in a scrolling format. Unlike the static nature of television, the Web enables users to interact with these tickers. If something catches their eye, they can click the news item and it takes them to their desired information. Because of this interactivity, news tickers are quite popular on web sites, and as it turns out, easy to implement using Ajax (see Figure 7-1).

Like any other Ajax-enabled application, a web-based news ticker comes in two parts: a server application and a client application. Since RSS feeds can exist on any server, a PHP server-side proxy is used to retrieve requested feeds for the client. The client application is, of course, a mix of HTML and JavaScript.

Figure 7.1. Figure 7-1

7.4.1. The Server-Side Component

The PHP server application is extremely simple. Its only function is to retrieve data from a URL and return it back to the client. To do so, the page expects a url variable in the query string to indicate which data to retrieve. For instance:

newsticker.php?url=http://rss.news.yahoo.com/rss/topstories

This URL tells newsticker.php, the server-side component, to retrieve data from the Yahoo! News Top Stories RSS feed.

Because the server's only job is to retrieve remote information, the code is only a few lines long:

<?php
header("Content-Type: text/xml");
header("Cache-Control: no-cache");

if ( isset( $_GET["url"] ) ) {
    $remoteUrl = $_GET["url"];

    $xml = file_get_contents($remoteUrl);

    echo $xml;
} else {
    header("HTTP/1.1 400 Bad Request");
}

?>

The first two lines set the Content-Type and Cache-Control headers, respectively. It is important to set the MIME content type to text/xml; otherwise, Mozilla Firefox doesn't recognize the data as XML and won't automatically parse it into an XML DOM document. It also is important to set the Cache-Control header to no-cache because Internet Explorer caches all data retrieved via XHR unless explicitly told not to.

In the next line of code, the query string is checked for the url parameter. To do this, use the isset() function, which returns a Boolean value based on whether a variable, function, or object exists. If a value has been passed in, the value of url is assigned to the $remoteUrl variable and passed to file_get_contents(). This function opens a file (local or remote), reads the file, and returns its contents as a string. The last step is to write the file's contents, stored in the $xml variable, to the page. This concludes the server-side code.

If the url parameter cannot be found in the query string, PHP returns an HTTP status of 400, which signifies a bad request. Because XParser is responsible for making requests to the server, it will handle this HTTP status and throw an error specifying that XHR failed to retrieve the data.

7.4.2. The Client-Side Component

Before delving into the code, consider the client's required functionality. The client:

  1. Builds the HTML to display the news feeds.

  2. Requests data from the server application. When the server responds with the requested data, the client parses the data with XParser.

  3. Places the parsed data into the HTML.

  4. Uses JavaScript to animate the ticker.

  5. Polls for updated data every 1.5 minutes.

In addition, a few user interface criteria must be met:

  • The data in the ticker, news article titles, should be links that take the user to the specified news article.

  • The ticker should stop scrolling when the user's mouse enters the ticker and should resume scrolling when the user mouses out.

To implement this functionality, the client-side code consists of two classes: the NewsTicker class, which builds the ticker in HTML format, animates the ticker, and provides the ability to add news feeds into the ticker, and the NewsTickerFeed class, which requests the feed, parses it, places it in the HTML, and polls for new data.

7.4.2.1. The NewsTicker Class

The NewsTicker class is the main class of the client-side code. The constructor accepts one argument, which is the HTMLElement to append the news ticker:

function NewsTicker(oAppendTo) {
    var oThis = this;
    this.timer = null;
    this.feeds = [];

    //more code to come
}

These first few lines of code initialize the properties of the NewsTicker class. First, a pointer to the object is created by assigning the variable oThis. The timer property, initially set to null, will control the scrolling animation (setTimeout() returns a unique timer identifier). The feeds property is an array that will contain NewsTickerFeeds objects.

Next, elements are created for the primary user interface of the news ticker:

function NewsTicker(oAppendTo) {
    var oThis = this;
    this.timer = null;
    this.feeds = [];
    this.tickerContainer = document.createElement("div");
    this.ticker = document.createElement("div");
    this.tickerContainer.className = "newsTickerContainer";
    this.ticker.className = "newsTicker";

    //more code to come
}

These properties, tickerContainer and ticker, reference newly created <div/> elements. The tickerContainer element does what its name implies: it contains all elements of the widget, whereas the ticker element scrolls the news feeds contained in it. The HTML code output by this constructor is:

<div class="newsTickerContainer">
    <div class="newsTicker"></div>
</div>

As a part of the user interface, remember that the scrolling animation stops when users move their mouse over the news ticker. To facilitate this functionality, event handlers are assigned for the onmouseover and onmouseout events of tickerContainer:

function NewsTicker(oAppendTo) {
    var oThis = this;
    this.timer = null;
    this.feeds = [];
    this.tickerContainer = document.createElement("div");
    this.ticker = document.createElement("div");
    this.tickerContainer.className = "newsTickerContainer";
    this.ticker.className = "newsTicker";

    this.tickerContainer.onmouseover = function () {
        oThis.stopTick();
    };

    this.tickerContainer.onmouseout = function () {
        oThis.tick();
    };
}

In the onmouseover event handler, the stopTick() method clears the timer property, which stops the animation. Notice the use of the oThis pointer, since the scope changes inside the event handler. The onmouseout event handler causes the animation to begin again by calling the tick() method, which performs the animation.

The next step is to append the ticker element to tickerContainer and to append the widget's HTML to its parent HTMLElement:

function NewsTicker(oAppendTo) {
    var oThis = this;
    this.timer = null;
    this.feeds = [];
    this.tickerContainer = document.createElement("div");
    this.ticker = document.createElement("div");

    this.tickerContainer.className = "newsTickerContainer";
    this.ticker.className = "newsTicker";

    this.tickerContainer.onmouseover = function () {
        clearTimeout(oThis.timer);
    };

    this.tickerContainer.onmouseout = function () {
        oThis.tick();
    };

    this.tickerContainer.appendChild(this.ticker);

    var oToAppend = (oAppendTo)?oAppendTo:document.body;
    oToAppend.appendChild(this.tickerContainer);

    //more code to come
}

The first line of this code appends ticker to tickerContainer, which completes the HTML layout. The next line offers a convenience for developers: if oAppendTo exists, then the widget is appended to the value of oAppendTo. If it doesn't, the HTML is appended to document.body. This gives the argument a default value; to append the widget directly to the document, the argument can be omitted.

The final lines of the constructor initialize the ticker:

function NewsTicker(oAppendTo) {
    var oThis = this;
    this.timer = null;
    this.feeds = [];
    this.tickerContainer = document.createElement("div");
    this.ticker = document.createElement("div");

    this.tickerContainer.className = "newsTickerContainer";
    this.ticker.className = "newsTicker";

    this.tickerContainer.onmouseover = function () {
        clearTimeout(oThis.timer);
    };

    this.tickerContainer.onmouseout = function () {
        oThis.tick();
    };

    this.tickerContainer.appendChild(this.ticker);

    var oToAppend = (oAppendTo)?oAppendTo:document.body;
    oToAppend.appendChild(this.tickerContainer);

    this.ticker.style.left = this.tickerContainer.offsetWidth + "px";
    this.tick();
}

This code positions the ticker at the farthest right edge of tickerContainer (the animation scrolls from right to left) and calls tick() to start the animation.

Internet Explorer and Firefox have different modes in which they render markup differently according to the doctype specified in the HTML page. Under what is known as standards mode, you must add "px" to any pixel measurement or the browser will not position the element.

7.4.2.1.1. Animating the Ticker

The basic logic of any animation is to move an element by a set amount of pixels repeatedly and at set intervals until the element reaches a specific location. The scrolling animation used in this widget is probably the simplest type of animation you can perform: a linear, right-to-left movement until the ticker's right edge reaches the container's left edge. The leftmost limit of the animation can be expressed by this.ticker.offsetWidth, which gives the element's width in pixels and then negates it to ensure that the entire element is not visible. When the ticker reaches this position in the page, the animation restarts. The tick() method begins by gathering this information:

NewsTicker.prototype.tick = function () {
    var iTickerLength = this.ticker.offsetWidth;

var oThis = this;

    //more code to come
};

The iTickerWidth variable contains the ending point of the animation: the negative offsetWidth of the ticker. Once again, a pointer to the NewsTicker object is assigned to oThis for later event handler assignments.

The first step in the animation is to decide whether the ticker contains any data, because there's no use in scrolling an empty <div/> element:

NewsTicker.prototype.tick = function () {
    var iTickerLength = this.ticker.offsetWidth;
    var oThis = this;

    if (this.ticker.innerHTML) {
        if (this.ticker.offsetLeft > -iTickerLength) {
            var iNewLeft = this.ticker.offsetLeft - 1;
            this.ticker.style.left = iNewLeft + "px";
        } else {
            this.ticker.style.left = this.tickerContainer.offsetWidth + "px";
        }
    }

    //more code to come
};

This code checks the element's innerHTML property; any HTML present in the ticker means that data exists and the animation should begin. The location of the ticker (offsetLeft) is checked against the animation's boundary (iTickerLength). If the location is greater than the limit, the animation continues. The next line gets the new left position of the ticker: one pixel to the left. The last line of this code block sets the left position to reflect the value contained in iNewLeft. This, however, is only one part of the animation. The ticker continues to move until it reaches the boundary; therefore, the ticker must be reset to its original location.

The last step is to perform an animation. Animations are implemented in JavaScript using a timeout that repeatedly calls a function in charge of moving an element. In the case of this animation, that function is the tick() method itself, so a wrapper function must be created and passed into the setTimeout() function:

NewsTicker.prototype.tick = function () {
    var iTickerLength = this.ticker.offsetWidth;
    var oThis = this;

    if (this.ticker.innerHTML) {
        if (this.ticker.offsetLeft > -iTickerLength) {
            var iNewLeft = this.ticker.offsetLeft - 1;
            this.ticker.style.left = iNewLeft + "px";
        } else {
            this.ticker.style.left = this.tickerContainer.offsetWidth + "px";

}
    }

    var doSetTimeout = function() {
        oThis.tick();
    };
    this.timer = setTimeout(doSetTimeout,1);
};

This last bit of code sets a timeout for the doSetTimeout() function, which simply calls tick() again. Doing so causes tick() to run every millisecond, so the animation continues until it is stopped by clearing the timeout (when the user mouses over the container).

7.4.2.1.2. Stopping the Animation

Anything that is started must, at some point, be stopped; so it is with the news ticker animation: the animation stops when the user moves their mouse pointer over the ticker. The mouseover event handler calls the stopTick() method:

NewsTicker.prototype.stopTick = function () {
    clearTimeout(this.timer);
    this.timer = null;
};

The timer property is passed to the clearTimeout() function, canceling the next code execution. Even though the timeout is cleared at that point, the timer property still holds the numeric value of that timeout; therefore, assign the property the value of null.

7.4.2.1.3. Adding Feeds

Now that the animation and HTML layout are complete, the only step left is to add feeds to the ticker. To facilitate this action, the NewsTicker class needs an add() method. This method accepts a single argument, which is the URL of a remote feed:

NewsTicker.prototype.add = function (sUrl) {
    this.feeds.push(new NewsTickerFeed(this, sUrl));
};

When this code executes, it creates a new NewsTickerFeed object and adds the object to the feeds array. This array is only used to initially load the feed data when the news ticker is created.

7.4.2.1.4. Removing the News Ticker

The final method of the NewsTicker class is the dispose() method. This method's job is to remove the ticker from the Web page and clean up the associated memory:

NewsTicker.prototype.dispose = function () {
    for (var i = 0; i < this.feeds.length; i++) {
        this.feeds[i].dispose();
    }

    //more code to come
};

The first step in this process is the removal of all feeds associated with this ticker, as this code demonstrates by looping through the feeds array and calling the dispose() method of the individual NewsTickerFeed objects. Next, the animation must be stopped by calling the stopTick() method, and the references to the various DOM elements must be deleted:

NewsTicker.prototype.dispose = function () {
    for (var i = 0; i < this.feeds.length; i++) {
        this.feeds[i].dispose();
    }

    this.stopTick();

    this.tickerContainer.parentNode.removeChild(this.tickerContainer);
    this.ticker = null;
    this.tickerContainer = null;
};

This code stops the animation and removes the HTML elements from the page, setting the ticker and tickerContainer properties to null (doing so prepares the object for the garbage collector).

7.4.2.2. The NewsTickerFeed Class

A news ticker isn't very useful without content to display. The NewsTickerFeed class pulls the required feeds, parses them with XParser, and assembles the HTML for the ticker. The constructor accepts two arguments: a reference to its the NewsTicker object (this allows access to the NewsTicker properties and methods when needed) and the URL of the feed to download:

function NewsTickerFeed(oParent, sUrl) {
    this.parent = oParent;
    this.url = sUrl;
    this.timer = null;
    this.container = null;

    this.poll();
}

Compared to the NewsTicker class's constructor, the NewsTickerFeed constructor is relatively simple. This class has four properties: parent (a reference to the parent NewsTicker object); url (the URL of the feed); timer (the reference used in the timeout for updating the feed); and container (the <span/> element containing the feed's information in the ticker). The last step in the constructor is to call the poll() method, which makes a request to the server to retrieve the feed.

7.4.2.2.1. Polling for New Information

The poll() method automatically checks for feed updates every minute and a half (this can be configured based on your needs):

NewsTickerFeed.prototype.poll = function () {
    var oThis = this;
    var sFullUrl = "newsticker.php?url=" + encodeURIComponent(this.url);
    xparser.getFeed(sFullUrl, this.populateTicker, this);
}

This code uses XParser to retrieve the XML feed. Before calling xparser.getFeed(), the URL is built, with the feed URL string being encoded by the encodeURIComponent() JavaScript function. It is important to encode the URL, because this ensures that any characters such as white space, ampersands, quotation marks, and so on are converted to their corresponding escape sequence for proper transmission. The code uses populateTicker as the callback for the request and asks it to be fired within the NewsTickerFeed object's scope.

One final addition to poll() is the automatic updating. To facilitate this, use an approach similar to the tick() method of the NewsTicker class:

NewsTickerFeed.prototype.poll = function () {
    var oThis = this;
    var sFullUrl = "newsticker.php?url=" + encodeURIComponent(this.url);
    xparser.getFeed(sFullUrl, this.populateTicker, this);

    var doSetTimeout = function () {
        oThis.poll();
    };

    this.timer = setTimeout(doSetTimeout, 90000);
}

This new code creates a function called doSetTimeout() to pass to the setTimeout() method. Because this version of doSetTimeout() exists only in the scope of the poll() method, it will not interfere with the previous function of the same name in tick(). The poll() method is now set to run every 1.5 minutes (every 90,000 milliseconds) and will update the feed.

7.4.2.2.2. Stop Automatic Polling

There may be instances where you want to stop a feed from updating. Doing so is as simple as the calling stopPolling():

NewsTickerFeed.prototype.stopPolling = function () {
    clearTimeout(this.timer);
    this.timer = null;
};

This method simply clears the timeout used for polling and assigns the value of null to the timer property.

7.4.2.2.3. Adding Content

When XParser finishes parsing the remote feed, it calls the populateTicker() method and passes itself as an argument. With the supplied XParser object, you can start to create the HTML:

NewsTickerFeed.prototype.populateTicker = function (oParser) {
    var spanTickerLinks = document.createElement("span");

    var aFeedTitle = document.createElement("a");
    aFeedTitle.className = "newsTicker-feedTitle";
    aFeedTitle.href = oParser.link.value;
    aFeedTitle.target = "_new";

aFeedTitle.innerHTML = oParser.title.value;

    spanTickerLinks.appendChild(aFeedTitle);

    //more code to come
}

The first step is to create an element to encapsulate all the links. This element serves the purpose of convenience: when the feed is updated, it is easier to remove one element with several children than it is to remove several elements one at a time. Also, don't confuse this container with the container property. The latter contains spanTickerLinks.

To separate the different feeds in the ticker, the feed's title is used. This is also a link, so if the user clicks on the title, a new window pops up taking him or her to the feed's web site. This link is given a CSS class of newsTicker-feedTitle and is appended to spanTickerLinks.

Next, create the link items by iterating through the items array of the XParser object:

NewsTickerFeed.prototype.populateTicker = function (oParser) {
    var spanTickerLinks = document.createElement("span");

    var aFeedTitle = document.createElement("a");
    aFeedTitle.className = "newsTicker-feedTitle";
    aFeedTitle.href = oParser.link.value;
    aFeedTitle.target = "_new";
    aFeedTitle.innerHTML = oParser.title.value;

    spanTickerLinks.appendChild(aFeedTitle);

    for (var i = 0; i < oParser.items.length; i++) {
        var item = oParser.items[i];

        var aFeedLink = document.createElement("a");
        aFeedLink.href = item.link.value;
        aFeedLink.target = "_blank";
        aFeedLink.className = "newsTicker-feedItem";
        aFeedLink.innerHTML = item.title.value;

        spanLinkContainer.appendChild(aFeedLink);
    }
}

Each link opens a new window when clicked and has a CSS class of newsTicker-feedItem. When the link is completed, it is appended to spanLinkContainer, which is then added to the ticker:

NewsTickerFeed.prototype.populateTicker = function (oParser) {
    var spanLinkContainer = document.createElement("span");

    var aFeedTitle = document.createElement("a");
    aFeedTitle.className = "newsTicker-feedTitle";
    aFeedTitle.href = oParser.link.value;
    aFeedTitle.target = "_new";

aFeedTitle.innerHTML = oParser.title.value;

    spanLinkContainer.appendChild(aFeedTitle);

    for (var i = 0, itemsLength = oParser.items.length; i < itemsLength; i++) {
        var item = oParser.items[i];

        var aFeedLink = document.createElement("a");
        aFeedLink.href = item.link.value;
        aFeedLink.target = "_new";
        aFeedLink.className = "newsTicker-feedItem";
        aFeedLink.innerHTML = item.title.value;

        spanLinkContainer.appendChild(aFeedLink);
    }
    if (!this.container) {
        this.container = document.createElement("span");
        this.container.className = "newsTicker-feedContainer";
        this.parent.ticker.appendChild(this.container);
    } else {
        this.container.removeChild(this.container.firstChild);
    }

    this.container.appendChild(spanLinkContainer);
}

When a NewsTickerFeed class is first created, the container property is declared but given a null value. This is done for a couple of reasons. First, the ticker's animation does not begin until it contains HTML. To keep the animation from running prematurely, the element referenced by container should not be added until the feed's data is retrieved, parsed, and assembled into HTML. This means that appending the container to the ticker should occur in populateTicker().

Second, because this operation takes place in populateTicker(), it is important not to add the same container to the ticker over and over again). Therefore, when the previous code executes, it checks if container has been initialized. If not, the <span/> element is created and appended to the ticker; otherwise, the link container is removed from container, and the newly created link container is added to the widget.

7.4.2.2.4. Removing Data

There may be a case where a feed needs to be removed from the ticker, either to be replaced or just simply to free up space. In that case, it's important to free up any memory used by the NewsTickerFeed object. This is where the dispose() method takes over.

Like the NewsTicker method of the same name, the NewsTickerFeed's dispose() method performs the removal of the feed from the ticker:

NewsTickerFeed.prototype.dispose = function () {
    if (this.timer) this.stopPolling();
    if (this.container) {
        this.parent.ticker.removeChild(this.container);

this.container = null;
    }

    this.parent = null;
};

The first line checks to see if the feed still automatically updates itself (remember, the timer property is assigned the value of null when stopPolling() is called). If so, then it is stopped from doing so. It then checks for, and removes, the HTML elements used by the NewsTickerFeed object. And last, it removes the reference to the NewsTicker object that contained the feed.

7.4.3. Styling the News

Since no two sites are the same visually, the ability to style the news ticker is very important. Before looking at the CSS, however, it is important to review the HTML structure of the news ticker:

<div class="newsTickerContainer">
    <div class="newsTicker">
        <span class="newsTicker-feedContainer">
            <span>
                <a />
                <a />
            </span>
        </span>
        <span class="newsTicker-feedContainer">
            <span>
                <a />
                <a />
            </span>
        </span>
    </div>
</div>

The outermost <div/> element is important for two reasons. First, it encapsulates every part of the widget. Second, it is the viewing box for the news items. Because it contains every element in the widget, it must be an extremely wide box, but you don't want to all the data seen until it enters the visible area. Therefore, the CSS overflow property must be set to "hidden":

.newsTickerContainer {
    overflow: hidden;
    position: relative;
    background-color: silver;
    height: 20px;
    width: 100%;
    padding-top: 2px;
}

Setting the overflow property to "hidden" hides any content that is not positioned within the specified area. Next, the position property is set to "relative." Other CSS properties can be customized depending on where the news ticker is being used; in this example code, height, width, padding, and background-color are assigned.

The next element contains all the feed links. This <div/> element is absolutely positioned so that it can be moved with JavaScript:

.newsTicker {
    white-space: nowrap;
    position: absolute;
    height: 25px;
}

Note also that the white-space property is set to nowrap, which disallows line breaks in the text. This is important because, otherwise, the text could end up on multiple lines instead of a single line.

The last two elements exposing CSS classes are the links: newsTicker-feedTitle and newsTicker-feedItem. The first is the link to the news site. Although none of the following properties is required, they set the feed's title apart from the remaining links:

.newsTicker-feedTitle {
    margin: 0px 6px 0px 6px;
    font-weight: bold;
    color: black;
    text-decoration: none;
}

There are six pixels of space on the left and right sides, giving distance between the feed items. The text is bold, is black, and has no underline, thus causing more separation in likeness between this link and the others.

The only formatting the feed items have are four pixels of space on each side, giving the links a defined look while still maintaining what the user expects:

.newsTicker-feedItem {
    padding: 4px;
}

The beauty of CSS is its ability to change the look and feel of any page or widget, regardless of markup (in most circumstances). Feel free to experiment with different CSS properties to format the news ticker to your specifications.

7.4.4. Using the News Ticker Widget

Since the back-end code is PHP, setting up this widget is as simple as uploading files and referencing them in your HTML. To add the JavaScript and CSS into your page, simply add the <script/> and <link/> tags:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Ajax News Ticker</title>

<link rel="stylesheet" type="text/css" href="css/newsticker.css" />
    <script type="text/javascript" src="js/zxml.js"></script>
    <script type="text/javascript" src="js/xparser.js"></script>
    <script type="text/javascript" src="js/newsticker.js"></script>
</head>
<body>


</body>
</html>

You'll also need to instantiate a new instance of NewsTicker. Remember, NewsTicker adds itself to an HTMLElement, so it's best to create the object when the page loads with the onload event:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>Ajax News Ticker</title>
    <link rel="stylesheet" type="text/css" href="css/newsticker.css" />
    <script type="text/javascript" src="js/zxml.js"></script>
    <script type="text/javascript" src="js/xparser.js"></script>
    <script type="text/javascript" src="js/newsticker.js"></script>
    <script type="text/javascript">
    window.onload = function() {
        var newsTicker = new NewsTicker();
        newsTicker.add("http://rss.news.yahoo.com/rss/topstories");
    }

    </script>
</head>
<body>


</body>
</html>

Because this widget uses XParser to parse the news feeds, any RSS 2.0 and Atom feed can be used with this widget. (The preceding example pulls the Yahoo! Top Stories feed.) The news ticker elements will be created inside the document's <body/> element because no container object was passed in to the NewsTicker constructor.

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

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