HTML-Market

How To Build A Live Stock Ticker - Part 2

Jan 23, 2007 10:13 pm
by: Shane Goodwin

In part 1 of this series, we built a "widget" to display a number of stock quotes on a website. In this part of the series, we will be harnessing the power of AJAX to automatically update those quotes at a predetermined interval. It is recommended, although not necessary, that you understand the XMLHTTPRequest object before reading this tutorial. It is necessary to be very familiar writing HTML and CSS code.

Note: (April 20, 2008) Some people have mentioned that the code below had a number of errors. I have tried to fix them, but there still may be a few lingering errors in the code. Also, if you are on a free web host like me, there is a good chance that your host will restrict your ability to retrieve quotes simply because of bandwidth issues.

This article uses PHP code written in the first part of this series of tutorials. If you do not understand the PHP code in this application, please see Part 1 of this article for a detailed explanation of the code.

The XMLHTTPRequest Object... and fixing Microsoft

This is the heart of any AJAX application. If you are not familiar with how this object works, a great tutorial is available at IBM's Developer Works. Of course Microsoft's IE5 and IE6 don't support this object so we have to accommodate for the 60% of users who still still use these antiquated browsers. Here is the code for this object and its IE fixes:

function createRequest(url) {
var http_request;

if (window.XMLHttpRequest) { // Mozilla, Safari, ...
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType) {
http_request.overrideMimeType('text/xml');
}
} else if (window.ActiveXObject) { // IE
try {
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
return http_request;
}
if (!http_request) {
return false;
}
}

AJAX Functions and the DOM

Now that we have provided a method for the browser to send a request to the server, we need to tell it what to send, where to send it, and what method to use to send our request:

http_request.onreadystatechange = function() { getQuotes(http_request); };
http_request.open('POST', url, true);
http_request.send(null);
}

This is really just a continuation of the XMLHTTPRequest object and provides the what, where, and method to send our request. The first line here simply provides an action for the code to take when the "readystate" of our request changes. Depending on which browser our user is on, there may be between 2 and 4 readystates. On every "readystate" change, the code will run the function "getQuotes(http_request) that we will build next. The second line tells the browser to send our request using the "POST" method and provides the location for the request to be send via the variable "url" which will be defined later in the tutorial. The last line gives the command to send the request with the object to be sent in parentheses; in this case, we are not actually sending anything, so its value is "null".

The next line details the function "getQuotes(http_request);":

function getQuotes(http_request) { 
if (http_request.readyState == 4) {
if (http_request.status == 200) {
document.getElementById('quotes').innerHTML = http_request.responseText;
document.getElementById('download').style.visibility = 'hidden';
getTime();
} else {
document.getElementById('quotebar').innerHTML = '<br>Stock quotes unavailable.<br><br>Please try again later.';
}
}
}

Keep in mind that this function is run every single time the "readystate" of our request changes. Using a few "if" functions we can get the code to only act on the information if its "readystate" = "4" and its "status" = "200". Together these two mean that everything is finished and ready to be used. Here we begin using the Document Object Model to dynamically change the contents of various divs in our web page. We do this by changing the innerHTML of an object in our web page to the content of our choosing. If you remember from the first tutorial in this series, "quotes" is the id of the div that contained our PHP stock price code. Here we are inserting the result of our call to the server into this div. Just so you know, the results that we are inserting into the div is of course the stock price information from our PHP code. We will see later that the PHP code has been removed from inside the div and placed into a separate file. The next line sets the visibility of another div with an id of "download" to hidden so that we cannot see it. This div is a download status indicator that will be created in our main file later on. Next, we run the function getTime() which is another function that will be defined later in this tutorial.

If the call to the server is finished but the status is something other than the normal "200", we insert some text into our "quotes" div to inform the user that the stock quotes have failed to load. Be sure to add the ending brackets at the end to complete the function.

Here is this complete file:

function createRequest(url) {
var http_request;

if (window.XMLHttpRequest) { // Mozilla, Safari, ...
http_request = new XMLHttpRequest();
if (http_request.overrideMimeType) {
http_request.overrideMimeType('text/xml');
}
} else if (window.ActiveXObject) { // IE
try {
http_request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
return http_request;
}
if (!http_request) {
return false;
}
}
http_request.onreadystatechange = function() { getQuotes(http_request); };
http_request.open('POST', url, true);
http_request.send(null);
}
 
function getQuotes(http_request) {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
document.getElementById('quotebar').innerHTML = http_request.responseText;
document.getElementById('download').style.visibility = 'hidden';
getTime();
} else {
document.getElementById('quotebar').innerHTML = '<br>Stock quotes unavailable.<br><br>Please try again later.';
}
}
}

That finishes the first of three files that we will be using for this tutorial. Save this file as "stockQuote.js" and then open the PHP file we created in Part 1.

The PHP

The good news is that the PHP content is already finished. We just need to move it into its own file. Go to the file we created in the first tutorial and cut out all of the content between the "<?php" and "?>" tags. Then paste them into a new file and save this file as "quoteClass.php".

The code in this file should look like this or something similar if you made modifications: (remember that the "url" variable MUST be all on one line or the page will throw errors.)

<?php
class getStock {
function getQuote($stock) {
$url = sprintf("http://finance.yahoo.com/d/quotes.csv?s=$stock&f=sl1c1p2&e=.csv", $stock);
$fp = @fopen($url, 'r');
$data = @fgetcsv($fp, 1000, ', ');
fclose($fp);
$this->symbol = $data[0];
$this->price = number_format($data[1],2);
$this->change = number_format($data[2],2);
$this->percent = number_format($data[3],1);
}
}
$stocks = array('^DJI','GOOG','YHOO');
$count = 0;
$quote = new getStock;
echo "<b>STOCK QUOTES</b>n";
echo "<table class="t" border="0">";
foreach ($stocks as $stock) {
$quote->getQuote($stocks[$count++]);
echo "<tr><td class="sym">".$quote->symbol."</td>n";
echo "<td class="qte">$" .$quote->price. ",n";
echo '<span style="';
if ($quote->change > 0) {
echo 'color:#006600;">+' .$quote->change. ', +' .$quote->percent. '%';
} elseif ($quote->change < 0) {
echo 'color:#a32900;">' .$quote->change. ', ' .$quote->percent. '%';
} else {
echo 'font-size:100%;">' .$quote->change. ', ' .$quote->percent. '%';
}
echo "</span></td></tr>n";
}
echo "</table>n";
?>

That was easy, huh? We can now use this file to continually get the new stock prices from Yahoo. The first file we created will be the one making a request to this file for the new information. The file we will be building next will define the "url" variable for our "createRequest(url)" function to use, as well as defining the structure for our live stock ticker.

Requesting the New Quote

This is the last file in this tutorial and includes PHP code, Javascript code including Document Object Model code, and some HTML. It will tie all the files together so that they interact to provide an automatically updating stock ticker. Let's dive into the code:

<html> 
<head>
<script type="text/javascript" src="stockQuote.js"></script>
</head>
<body>
<script type="text/javascript">
<!--
var serverHour = <?php echo date("G"); ?>;
var serverDay = <?php echo date("w"); ?>;

The first part of this code are some simple HTML commands to get our new file prepared to be viewed in a browser. We also need to include our "stockQuote.js" file so that we can make use of its function. Following the "body" tag, we begin a new Javascript section. This part of the code simply creates two Javascript variables to hold the current hour and day as defined by the server. We are using PHP's echo command to obtain the current hour and day. The "date("G");" function is a built-in PHP function to obtain the current hour in a 24 hour format from the server and the "date("w");" function is a built-in PHP function to obtain the current numeric representation of the day of the week from the server. The fact that the current hour and day are defined are by the server is important here. My server resides in the central time zone of the United States. Your time zone will more than likely sit somewhere else and this will require a slight change in code later in this tutorial. There are a number of other PHP date format characters you can use in your website. An excellent description of the different format characters is available at Zend Developer Zone.

The next line of code is:

function requestquote() {
document.getElementById('download').style.visibility='visible';
createRequest('quoteClass.php');
}

This function's purpose is to do 3 things - make our "download" div visible in the browser, define the "url" variable from our "getQuote.js" file, and run the function "createRequest(url)" with "newQuote.php" as the variable value. Remember that previously we had set the "download" div to hidden - here we are making it visible so that our user can see that the quote is currently updating.

The next bit of code defines our "getTime()" function from earlier:

function getTime() { 
if ((serverDay > 0) && (serverDay < 6) && (serverHour > 7) && (serverHour < 16)) {
setTimeout("requestquote()", 57000);
} else {
setTimeout("getTime()", 1800000);
}
}
//-->
</script>

Before we get started breaking down this function, let me explain its purpose. Making requests to the server requires a small amount of bandwidth, doubly so since the server is then sending stock quotes to Yahoo. If we are continually making requests, this small amount of bandwidth can add up, especially if there are multiple users on your website at the same time. As a matter of fact, if you run a major website with thousands of hits a day, the stock ticker we are developing here may overload a server. An alternative would be to store the stock prices returned in a database and then rewrite the script to retrieve the stock prices from there instead of going to Yahoo for every request. However, that is beyond the scope of this tutorial. What we can do to limit the number of server requests is to set up our stock ticker to only retrieve new stock prices while the stock market is actually open. That's what this function is for.

In the last section of code we created Javascript variables to hold the day and hour returned by the server. Here we are using an "if" function to determine if the stock market is currently open. We know that the stock market is open Monday - Friday between 8:30am and 3:00pm central standard time. Our "serverday" variable returns a number between 0 and 6 where 0=Sunday, 1=Monday, etc. The "serverhour" returns a military time format between 0 and 23. Using inequalities we can set our function to only run if the day is Monday through Friday between 8:00am and 4:00 pm. This where the server time problem I mentioned earlier comes in to play - the hours need to be changed depending on which time zone your server is located in. If your server resides in California, for example, then you will need to change the times to reflect the correct hours the stock market is open - between 6:30am and 1:00 pm; so "> 5" and "< 14".

So, if the stock market is open, we use a "setTimeout" function to wait 57 seconds and then run the function "requestquote()" that we wrote earlier. Why wait 57 seconds? My goal was for the stock prices to update every minute. Since it takes about 3 seconds to download the new data, I need to wait 57 seconds before making the request so that I get a period of about 60 seconds between updates. Note, that you can change this number to whatever you want. If you set it too low though, then you may start causing problems since new requests will be made before the original requests are finished downloading. Also, if you have specified more stocks in the "$stocks" array of your PHP file, then the download time will be greater; so you may want to decrease the wait time a little to compensate. We also use an "else" function to perform an action if the stock market is not currently open. In this case, we use a "setTimout" function to wait 30 minutes and then run the "getTime()" function all over again. This way if someone has the stock ticker open early in the morning, the stock ticker will begin retrieving quotes just before the market opens. This finishes the Javascript we began earlier so we include the "</script>" command to alert the browser that what follows is HTML.

The Quote Structure

Now for some simple HTML:

<div id="quotes"> 
<div id="download">downloading...</div>
<div id="quotebar">Loading Stock Quotes...</div>
</div>

This is the structure of our stock ticker. As with our stock ticker from Part 1, all of our stock price information will be enclosed within a div with an id of "quotes". Next is the disappearing and reappearing div "download" that we have been changing in our previous code. This div contains the word "downloading.." so that our user knows when the new quotes are updating.

The last chunk of code is:

<script type="text/javascript"> 
<!--
createRequest('quoteClass.php');
//-->
</script>
</body>
</html>

We are starting a new Javascript here that will download the stock quotes when the stock ticker page initially loads. We do that by running the function "createRequest(url);" with "newQuote.php" as its value just like we did earlier in the page. If you remember, one of this function's commands is to start the "getTime()" function which starts the stock ticker on its way toward being updated in 60 seconds. Be sure to add the closing tags for the script and the html page. Then save this page with a ".php" extension and we are done with this page.

Here is the complete PHP file:

<html> 
<head>
<script type="text/javascript" src="stockQuote.js"></script>
</head>
<body>
<script type="text/javascript">
<!--
var serverHour = <?php echo date("G"); ?>;
var serverDay = <?php echo date("w"); ?>;
function requestquote() {
document.getElementById('download').style.visibility='visible';
createRequest('quoteClass.php');
}
function getTime() {
if ((serverDay > 0) && (serverDay < 6) && (serverHour > 7) && (serverHour < 16)) {
setTimeout("requestquote()", 57000);
} else {
setTimeout("getTime()", 1800000);
}
}
//-->
</script>
<div id="quotes">
<div id="download">downloading...</div>
<div id="quotebar">Loading Stock Quotes...</div>
</div>
<script type="text/javascript">
<!--
createRequest('quoteClass.php');
//-->
</script>
</body>
</html>

The only thing left to do is style the "download" div so that it starts out as hidden:

#download { visibility:hidden; }

Include this somewhere either in your own CSS file or between the "head" tags as an embedded style. Congratulations! You now have an automatically updating stock ticker that can be used either as a standalone page or as part of any web page you have. To include it as part of your web page, just combine all of the code from the last file into your own existing web page, position the "quotes" div using CSS, and place the other two files we built into the same directory.

Related Articles:

 How to Build a Live Stock Ticker - Part 1