JavaScript EditorFreeware JavaScript Editor     Ajax Tutorials 



Main Page

Previous Page
Next Page

Creating a News Ticker Widget

Popular on both television news networks and web sites, the news ticker displays current events in a scrolling format. Unlike the static nature of television, the Web enables users to interact with these tickers. If something catches their eyes, they can click the news item and it takes them to their desired information.

The widget built in this section, a news ticker, provides this functionality (see Figure 8-1). Like any other Ajax-enabled application, it comes in two parts: a server application and a client application.

Image from book
Figure 8-1

For this project, you will use PHP to perform the server's function: to behave as a proxy to retrieve requested feeds for the client. The client application consists of the usual suspects: DHTML and JavaScript.

The Server-Side Component

The server application, written in PHP as already stated, is extremely simple. To request information from the server, the URL should contain a url variable in the query string:

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

<?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;
}

?>

The first two lines set the Content-Type and Cache-Control headers, respectively. As mentioned in Chapter 5, it is important to set the MIME content type to text/xml; otherwise, Mozilla Firefox doesn't recognize the data as XML. It also is important to set the Cache-Control header to no-cache because Internet Explorer caches all data retrieved via XMLHttp unless explicitly told not to.

In the next line of code, the query string is checked for the url query item. To do this, use the isset() function, which returns a Boolean value based on whether a variable, function, or object exists. 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. Last, the file's contents, stored in the $xml variable, are written to the page.

With the proxy completed, a client needs to be authored to take advantage of the proxy's service.

The Client-Side Component

Before delving into the code, the client's functionality should be discussed. 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 DHTML 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 resume scrolling when the user mouses out.

The client-side code consists of two classes: the NewsTicker class, which builds the ticker in HTML format, animates the ticker with a scrolling animation, 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.

The NewsTicker Class

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

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

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 of NewsTickerFeeds objects.

The next two properties create the primary elements of this widget:

this.tickerContainer = document.createElement("div");
this.ticker = document.createElement("nobr");

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

These properties, tickerContainer and ticker, reference newly created <div/> and <nobr/> elements, respectively. 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 <nobr/> tag is chosen because it does not allow any breaks in the text; otherwise, text would pop into view instead of scrolling as a result of word wrapping. The HTML code output by this constructor is:

<div class=" newsTickerContainer">
    <nobr class=" newsTicker"></nobr>
</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, assign event handlers for the onmouseover and onmouseout events of tickerContainer:

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

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

In the onmouseover event handler, clearTimeout() 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:

this.tickerContainer.appendChild(this.ticker);

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

The first line of this code appends ticker to tickerContainer, which results in the HTML seen earlier in this section. The next line offers a convenience for developers. If oAppendTo exists, the constructor's argument, then the widget is appended to the value of oAppendTo. If it doesn't, however, the HTML is appended to document.body. This gives the argument a default value, so if you want to append the widget directly to the document, you do not have to pass an argument.

The final lines of the constructor initialize the ticker:

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

The first line of code positions the ticker at the farthest right edge of tickerContainer. (The animation scrolls from right to left.)

Note

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 line of code that positions an element, or the browser will not position the element.

Finally, calling the tick() method starts the animation, and the final constructor code looks like this:

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

    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();
}
Doing the Animation

The basic logic of any animation is to move an element by a set amount of pixels and repeat this operation until the element reaches a specific location on the page. 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. This last part is the limit of the animation and can be expressed by -this.ticker.offsetWidth. When the ticker reaches this position in the page, the animation begins again.

The tick() method begins by gathering this information:

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

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.

To perform animations, a timeout is set at a low interval of time to repeat a function that moves the desired element. In the case of this animation, it is a function inside the tick() method:

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

You could use external variables, functions, or methods to aid in the animation, but that approach isn't very object oriented. Keeping the animation logic inside the tick() method makes the widget easier to deploy and maintain.

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

if (this.ticker.innerHTML) {

}

This code checks the element's innerHTML property; any HTML present in the ticker means data exists and the animation should begin:

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

This new code moves the ticker. The first new line checks the location of the ticker (offsetLeft) in relation to 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, you must reset the ticker to its original location:

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

    var doSetTimeout = function() {
        oThis.tick();

    };

    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";
        }
    }
    this.timer = setTimeout(doSetTimeout,1);
};

The first block of new code does this. At the end of the animation, the ticker is once again placed at the right edge of the container. The last new line sets a timeout for the doSetTimeout() function contained within tick(). This causes tick() to run every millisecond, so the animation is ongoing until it is stopped by clearing the timeout (when the user mouses over the container).

Adding Feeds

One final method of the NewsTicker class is the add() method, which adds a feed to the ticker and populates the feeds array:

NewsTicker.prototype.add = function (sUrl) {
    var feedsLength = this.feeds.length;

    this.feeds[feedsLength] = new NewsTickerFeed(this, sUrl);
};

This method accepts one argument: the URL to the remote feed. A NewsTickerFeed object is then created as an element of the feeds array. This array isn't used in any of the class's code, other than initializing and populating the array. It exists merely for convenience for developers that may want to access the different feeds in the ticker.

The NewsTickerFeed Class

A news ticker isn't so handy without content to display. The NewsTickerFeed class pulls the required feeds, parses them with XParser, and assembles and adds the HTML to the ticker. The class's constructor accepts two arguments: The first argument is a reference to its parent NewsTicker object; this allows access to the NewsTicker object's properties and methods when needed. The second argument is the URL of the feed you want to obtain.

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

    this.poll();
}

Compared to the NewsTicker class's constructor, the NewsTickerFeed class's constructor is relatively simple. This class has three properties: parent, a reference to the parent NewsTicker object; url, the URL of the feed; and container, a <span/> element that contains the feed's information.

Polling for New Information

The poll() method, called before the constructor exits, makes the request to the server application. It also automatically checks for updates every minute and a half:

NewsTickerFeed.prototype.poll = function () {
    var oThis = this;

    var oReq = zXmlHttp.createRequest();
    oReq.onreadystatechange = function () {
        if (oReq.readyState == 4) {
            if (oReq.status == 200) {
                oThis.populateTicker(oReq.responseText);
            }
        }
    };

    var sFullUrl = encodeURI("newsticker.php?url=" + this.url);

    oReq.open("GET", sFullUrl, true);
    oReq.send(null);
}

This code creates the XMLHttp object. First, you'll notice the ever-popular technique of assigning a pointer to the NewsTickerFeed object to oThis. The next code block creates an XMLHttp object with the zXml library and defines the onreadystatechange event handler. On a successful request, the responseText property is sent to the populateTicker() method.

Before making the actual request, it is important to encode the URL. This ensures any characters such as white space, ampersands, quote marks, and so on are converted to their corresponding escape sequence for proper transmission.

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

NewsTickerFeed.prototype.poll = function () {
    var oThis = this;

    var oReq = zXmlHttp.createRequest();
    oReq.onreadystatechange = function () {
        if (oReq.readyState == 4) {
            if (oReq.status == 200) {
                oThis.populateTicker(oReq.responseText);
            }
        }
    };

    var sFullUrl = encodeURI("newsticker.php?url=" + this.url);

    oReq.open("GET", sFullUrl, true);

    oReq.send(null);

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

    setTimeout(doSetTimeout, 90000);
}

This new code creates another 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 and will update the feed.

Adding Content

The final method of the NewsTickerFeed class is populateTicker(), mentioned earlier because it's called in poll(). This method accepts the XMLHttp responseText property as its argument and parses it with XParser:

NewsTickerFeed.prototype.populateTicker = function (sXml) {
    var oParser = new XParser(sXml, true);
}

By passing the second argument with the value of true, XParser treats the first argument as serialized XML and will not make its own XMLHttp request. With the XML now parsed, you can start to create the HTML:

NewsTickerFeed.prototype.populateTicker = function (sXml) {
    var oParser = new XParser(sXml, true);

    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);
}

The first step is to create an element to encapsulate all the links. This element serves a 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 spanLinkContainer.

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

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

NewsTickerFeed.prototype.populateTicker = function (sXml) {
    var oParser = new XParser(sXml, true);

    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; i < oParser.items.length; 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);
    }
}

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 you now add to the ticker:

NewsTickerFeed.prototype.populateTicker = function (sXml) {
    var oParser = new XParser(sXml, true);

    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; i < oParser.items.length; 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, remember the container property is declared but given a null value. This is done for a couple of reasons. First, remember that 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 makes appending the container to the ticker occur in populateTicker().

Second, because this operation takes place in populateTicker(), it is important to not add the same container to the ticker over and over again, or else the ticker will exhibit strange behavior (not to mention the browser could crash). 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.

Of course, displaying the data is meaningless if you don't spice it up to your own liking.

Styling the News

Before looking at the CSS, it is important to know the HTML structure:

<div class=" newsTickerContainer">
    <nobr class=" newsTicker">
        <span class=" newsTicker-feedContainer">
            <span>
                <a />
                <a />
            </span>
        </span>
        <span class=" newsTicker-feedContainer">
            <span>
                <a />
                <a />
            </span>
        </span>
    </nobr>
</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, you must set the CSS overflow property:

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

Setting the overflow property to hidden hides any contained content not currently visible. Also, set the position property to relative to ensure that the ticker moves in relation to newsTickerContainer and not the document's body. Any other CSS property is up to you to set. In the example code, the height, width, padding, and background color are assigned.

The next element, the ticker, contains all the feeds added to the widget. This <nobr/> element is absolutely positioned so that it can be moved through JavaScript:

.newsTicker {
    position: absolute;
    height: 25px;
}

The only required property is position, or else the ticker may not scroll. Any other CSS property can be placed to give it the look and feel you desire.

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 are 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 from the feed items. The text is bold, 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 (links!).

.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). Play around with it to come up with your own look to make it fit within your web page.

Implementing the News Ticker Widget

Because the back-end code is PHP, setting up this widget is as simple as uploading files and referencing them in your HTML. (PHP is required, of course). 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">
    function init() {
        var newsTicker = new NewsTicker();
        newsTicker.add("http://rss.news.yahoo.com/rss/topstories");
    }

    onload = init;
    </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). Cast away those boring blog rolls; make them scroll!


Previous Page
Next Page

R7


JavaScript EditorAjax Editor     Ajax Validator


©