The Observer Pattern
The Observer Pattern defines a one-to-many dependency between objects so that when one object (the Observee) changes state, all its dependents (the Observers) are notified and updated automatically. Most literature refers to the Observee as the Subject. For this article I've used the term "Observee" to emphasize the role of this objectit's the object responsible for downloading the news. The Observers are the objects interested in receiving the news.
The sample application implements the Observer using a JavaScript class called "Marquee," while the Subject (from now on I'll refer to it as Observee) is the "ContentRetriever" class. Note that, Marquee and ContentRetriever both represent the concrete classes of
Figure 3. They do not inherit from abstract classes because JavaScript doesn't strictly define abstract classes, a subject whicheven if we could "simulate" abstract classesgoes beyond the scope of this article. Marquee's responsibility is to display the information within a marquee element. ContentRetriever, on the other hand, takes care of downloading the news in JSON format, using AJAX calls.
ContentRetriever use a helper class, HtmlBuilder, to parse the retrieved JSON text and build the HTML code to be displayed within a marquee HTML element. This helper class isolates the presentation logic. If you wanted to change the news format you would need to modify only the logic in HtmlBuilder. The sample application uses a CSS stylesheet to define the fonts, colors and so on, which you can modify it according to your taste.
Here's the ContentRetriever class constructor:
function ContentRetriever(jsonDataRetriever, observerObjectsArray)
{
// JSON data retrieved and parsed
this.jsonData = "";
// URL of the Server-side component responsible
// for retrieving the JSON data
this.jsonDataRetriever = jsonDataRetriever;
// AJAX object
this.ajaxObj = createHttpRequest();
// array of observers
this.observerObjectsArray = observerObjectsArray;
}
The first parameter is a URL that points to the server-side component responsible for retrieving the news. The second parameter is an array of objects (observers) interested in being notified when the information gets retrieved by ContentRetriever (the observee). After all, this is the essence of the Observer Pattern: something doing a job and others just sitting around waiting to be notified when it's done! The function
createHttpRequest creates an XMLHttpRequest object, taking browser differences into account. This function is defined within the ajax.js file that you can find in the
downloadable code for this article. The file
demo.htm contains the JavaScript code that builds and displays the ticker:
<script type="text/javascript">
var marquee = new Marquee("newsMarquee", 2, true);
var contentRetriever = new ContentRetriever(
"./tickerDataRetriever.php", [marquee]);
//get the content
contentRetriever.getContent();
</script>
The preceding code creates an instance of the Marquee class, which I'll discuss shortly, and then builds a ContentRetriever object, passing in both the URL of the PHP script responsible for retrieving the data and an array of observer objectsin this case just one.
Here's the code of the Marquee constructor:
function Marquee(elemId, scrollamount, stopOnMouseOver)
{
this.elemId = elemId;
this.scrollamount = scrollamount;
this.stopOnMouseOver = stopOnMouseOver;
this.dataArray = [];
this.errorMessage = "No news available";
}
The Marquee constructor takes three parameters:
- elemIdThis is the id of the div element apt to display the news.
- scrollamountThe scrollamount attribute of the marquee HTML element. It indicates the scrolling speed.
- stopOnMouseOverA Boolean indicating whether news scrolling should be stopped when the user hovers the mouse over a news item.
The
dataArray property is the array containing the news items retrieved and parsed, and the
errorMessage property is an error message to display if the news download fails.
The method that actually retrieves the news is
getContent(), in the ContentRetriever class. Here's the code:
ContentRetriever.prototype.getContent = function()
{
if (this.ajaxObj)
{
var instance = this;
this.ajaxObj.open('GET', this.jsonDataRetriever, true);
//callback function
this.ajaxObj.onreadystatechange =
function(){instance.processDataRetrieved();};
this.ajaxObj.send(null);
}
};
First
getContent() checks whether the XMLHttpRequest object was created correctly. Then it prepares an AJAX call using the HTTP
GET method, the URL for the PHP news-retrieval script, and a Boolean specifying whether the call should be asynchronous (the third parameter (
true) of the
open() call). Because the call is asynchronous you must define a callback function that will execute when the call completes; in this case, that's
processDataRetrieved. Lastly it sends the AJAX call without any other data (using
null as the argument of
send()).
The server-side script that retrieves the actual datathe file
tickerDataRetriever.phpis very simple:
<?php
//news folder
$NEWS_FOLDER = "news";
//news file
$filename = "news.txt";
//name of the news file
$localFile = $NEWS_FOLDER . "/" . $filename;
//return the content as a JSON string
print(file_get_contents($localFile));
?>
For simplicity's sake, the news in JSON format was hard-coded within a text file. Of course, in a real world application, the information would be retrieved from a database, an RSS feed or whatever you might choose for dynamic content. In this case, the PHP script just retrieves the JSON text and passes it back to ContentRetriever. This is the content of news.txt:
{
"items" :
[
{
"title" : "This is the first item's title",
"link" : "http://www.alessandrolacava.com",
"description" : "This is the description of the \"first\" news.
The link takes you to my Web site"
},
{
"title" : "Second item's title",
"link" : "http://www.json.org",
"description" : "You can appreciate how easily you
can parse JSON text. This link takes you to the JSON Web site"
},
{
"title" : "This is the third item's title.",
"link" : "http://www.devx.com",
"description" :
"The link for this item takes you to the DevX Web site"
}
]
}
As you can see, the file content is just a JSON string representing an array of objects. Each object represents a news item and has three properties:
title,
link and
description. Notice the escaped double quotes (
\") in the first item's
description value. You must escape embedded double quotes; otherwise the parser will interpret them as the end of the field value. At the end of the article you'll see how to automate this process when retrieving dynamic content.
The following function processes the retrieved content (the JSON text):
ContentRetriever.prototype.processDataRetrieved = function()
{
//request completed
if (this.ajaxObj.readyState == 4)
{
//request succeeded
if (this.ajaxObj.status==200)
{
//check whether the response is empty
if(this.ajaxObj.responseText != null &&
this.ajaxObj.responseText.length > 0)
{
var htmlBuilder = new HtmlBuilder(this.ajaxObj.responseText);
//parse the JSON data
this.jsonData = htmlBuilder.getHtml();
}
else
{
//return a zero-length array if an error occurs
this.jsonData = [];
}
// notify observers
this.notify();
}
}
};
The preceding code first checks if the request completed successfully. Then it instantiates an HtmlBuilder object, passing in the JSON text. The HtmlBuilder's
getHtml method parses the JSON string and builds the HTML code to be displayed within the marquee. When it's done, it calls the
notify method:
//notify all observers
ContentRetriever.prototype.notify = function()
{
//call the observers' callback function, passing in the resulting array
for(var i = 0; i < this.observerObjectsArray.length; i++)
{
this.observerObjectsArray[i].update(this.jsonData);
}
};
The notify method calls the
update method of all the subscribed observersin this case just one, the marquee.
The HtmlBuilder
getHtml function does the transformation work of creating HTML from the returned JSON code:
//build the HTML code and return it as a JavaScript array of items
HtmlBuilder.prototype.getHtml = function()
{
var ret = [];
//decode the JSON text
var obj = this.decodeJson();
//get the array
var items = obj.items;
//build the HTML code using the array
for(var i = 0; i < items.length; i++)
{
var item = "";
//build the title
item += this.buildTitle(items[i]);
item += "<br />";
//build the description
item += this.buildDescription(items[i]);
item += "<br />";
ret.push(item);
}
return ret;
};
This function parses the JSON text and creates a set of HTML strings that it returns to ContentRetriever as a JavaScript array. The actual parsing of the JSON string occurs within the
decodeJson function:
HtmlBuilder.prototype.decodeJson = function()
{
return eval("(" + this.jsonString + ")");
};
As you can see it takes just
one line of code to parse the whole JSON data! Isn't that great? If you think it's not then, as an exercise, represent the same news using XML and parse it using JavaScript. I bet you'll change your mind at once!
The rest of
getHtml just formats the news conveniently and creates an array of news items. This array is the parameter passed to the
update method of Marquee that displays the news within a marquee HTML element.