The first article in this series, What's New In JavaFX 1.2 Technology: New Layouts and Effects, introduced you to new layout classes such as Unlike the many articles that concentrate on graphical user interface (GUI) features and application design in JavaFX technology, this article and the next will provide insight into the more technical features such as RSS and Atom tasks, local storage using JavaFX's built-in storage classes, and the use of JavaFX charts.
The
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Name |
Type |
Description |
|---|---|---|
interval |
Duration |
The amount of time before the feed is run again for updates. |
location |
String |
The location of the feed; in this case, it is http://www.quoterss.com/quote.php?symbol={s}&frmt=0&Freq=0 |
onChannel |
function(:Channel):Void |
Returns the channel element of the RSS feed. |
onException |
function(:Exception):Void |
If an exception occurred, this function will be executed. |
onItem |
function(:Item):Void |
Returns the current item element of the RSS feed. |
onDone |
function():Void |
Once the task has completed successfully or unsuccessfully, this function will be executed. |
As noted in the previous section, the information returned in the feed from QuoteRSS.com is one long string:
QuoteRSS.com: MSFT: 25.16 at 10:34am 9/21/2009
That is not very helpful if the application's user interface (UI) needs to use and display specific parts of that string to the end user. This is where combining the Java and JavaFX script technologies comes in very handy.
The following code is a function created to "scrape out" the elements needed for the StockReader application.
public function parseTitleString(inContent:String, symbol:String):String[] {
def startTitle = "QuoteRSS.com: {symbol}: ";
def endTitle = "";
var begPos = 0;
var endPos = 0;
var retStr:String;
var stockElements:String[];
begPos = inContent.indexOf(startTitle) + startTitle.length();
if (begPos >= 0) {
endPos = inContent.indexOf(endTitle, begPos) - 1;
retStr = inContent.substring(begPos);
}
stockElements = retStr.split(" ");
return stockElements;
}
This code allows you to pass in the item's title -- for example, the long string at the beginning of this section -- then to skip over the URL and stock symbol at the beginning of the string, and to split the remaining elements in between each space.
For instance, passing in the quote for that MSFT update of QuoteRSS.com: MSFT: 25.16 at 10:34am 9/21/2009 would provide the following:
["25.16","at","10:34am","9/21/2009"]
Now that the application can break the information down to what it needs, a face-lift of the UI is in order, as shown in Figure 2.

Each of the white squares in Figure 2 is a StockItemNode. Using the new layout class Tile, which was demonstrated in the previous article, the StockItemNodes are aligned horizontally and vertically in a tablelike format, wrapping to a new row once the nodes reach the width of the Tile.
In this example, the StockItemNode has its own data model, a StockItem, which is very simple:
package stockreader.model;
public class StockItem {
public var stockSymbol:String;
public var price:String;
public var time:String;
public var date:String;
}
At this point, you can modify the RSSTask from the previous section to do something other than print to the console. For each stock symbol in the model, the application will create a StockItem, as seen in the stockItems variable in the following code sample. Instead of running an RSSTask for each symbol, the code is modified to run for each StockItem. The data returned from the RSSTask will be assigned the StockItem variables shown in the previous code sample. Take a look at the model's new RSSTask:
public class StockReaderModel {
var feedTask:FeedTask;
public var symbols:String[];
public var stockItems:StockItem[]= bind for (s in symbols) StockItem {
stockSymbol: s
};
/**
* A function that will run a feed for each stock symbol,
* and create a StockItem data model for each.
*/
public function startFeeds():Void {
for (si in stockItems) {
println("symbol is {si.stockSymbol}");
feedTask = RssTask {
location: "http://www.quoterss.com/quote.php?symbol={si.stockSymbol}&frmt=0&Freq=0"
interval: 60m
onException: function(e) {
println("Exception is: {e}");
si.price = "{indexof si + 1}";
si.time = "Feed Error";
si.date = "Feed Error";
}
onChannel: function(channel) {
println("{channel.title}");
}
onItem: function(item) {
println("{item.title}");
si.price = parseTitleString(item.title, si.stockSymbol)[0];
si.time = parseTitleString(item.title, si.stockSymbol)[2];
si.date = parseTitleString(item.title, si.stockSymbol)[3];
}
onDone: function():Void {
feedTask.stop();
}
}
feedTask.start();
}
};
Note that the interval variable in the code sample has been changed to 60m, 60 minutes. You must assign a value to this variable in order for the RSSTask to work properly.
In this case, it is not desirable that the feed tasks run continually, because the stocks themselves are not updated at specific intervals. Therefore, it would make more sense to have the user "refresh" the stocks when desired. Because the interval variable must be set, this application uses a large time interval of 60 minutes and stops the feed, feedTask.stop();, in the RSSTask's onDone event handler.
But the application becomes much more useful if the end user is able to specify which stocks to watch. We added controls to allow the user either to choose from a list of predefined popular stocks or add some stocks by using a text box. When the user adds a stock symbol, that symbol is added to the symbols variable in the model, and the startFeeds() function is called, adding a new StockItemNode to the UI. Figure 3 shows the open dialog box and controls.

Sometimes an error occurs in the RSS feed. As noted in the previous section, there are many uses for the onException variable in the RSSTask. In this application, if the feed for a stock causes an error, we want to make sure that the user sees something, even if it is not the correct data.
Look at the following snippet of code, and you'll notice that the StockItems variables are still assigned something, but the update time and date now show "Feed Error." This way, the user knows that something is wrong.
onException: function(e) {
println("Exception is: {e}");
si.price = "{indexof si + 1}";
si.time = "Feed Error";
si.date = "Feed Error";
}
The StockReader application now has a UI that allows the user to add and delete stocks, and to update the stocks using the refresh button.
The next step is very important in this kind of application: local storage.
The ability to store a user's data is an incredibly useful feature and, in many cases, a requirement. In this article's example application, users would get frustrated if they had to add the same stocks every time they ran the program. Therefore, locally storing the user's stocks and loading them on startup would make the application both easier to use and more effective.
First, take a look at the saveProperties() function in the following code snippet:
var entry:Storage;
public function saveProperties():Void {
println("Storage.list():{Storage.list()}");
entry = Storage {
source: "stockreader.properties"
};
var resource:Resource = entry.resource;
var properties:Properties = new Properties();
def symbolsTemp = for (symbol in symbols) "{symbol},";
println("symbolsTemp looks like this: {symbolsTemp}");
properties.put("symbolsTemp", "{symbolsTemp}");
try {
var outputStream:OutputStream = resource.openOutputStream(true);
properties.store(outputStream);
outputStream.close();
println("properties written");
}
catch (ioe:IOException) {
println("IOException in saveProperties:{ioe}");
}
};
The Storage class contains two important variables that are used in the previous snippet: source and resource.
The source variable is simply the path to the resource. In this example, it is a filename: stockreader.properties.
The source can also be the absolute path to the resource, starting with /. A Resource is what will be stored on the platform, similar to a file. In the previous example, the stock symbols will be the resource.
A very important note is that the symbols used in the program are in a String array. When this sequence of strings is stored, it becomes a single string. For example, ["AAPL","MSFT","GOOG"] becomes "AAPLMSFTGOOG".
In preparation for this, the constant symbolsTemp is defined, creating a comma-separated list of symbols: "AAPL,MSFT,GOOG". This will make the stored symbols easy to split apart when loaded, and it will ensure that there are no extra spaces or characters when the symbols are stored.
Table 2 and Table 3 outline the variables and functions of the Storage and Resource classes that are used in the StockReader example application.
Name |
Type |
Description |
|---|---|---|
resource |
Resource |
The resource to be managed |
source |
String |
The path (or absolute path) to the resource |
Name |
Type |
Description |
|---|---|---|
name |
String |
The name of the resource |
openInputStream():InputStream |
|
Opens an InputStream from the resource |
openOutputStream():OutputStream |
|
Opens an OutputStream to the resource |
The Properties class is defined by the JavaFX 1.2 API as a "utility class for accessing and storing name/value pairs." The example creates a new instance of the Properties class and creates a name/value pair for symbolsTemp.
The put() function is passed a key:String, which is a name for the value to be stored, and a value:String, which in this case is symbolsTemp.
Now that things are set up, it's time to try storing the data using the resource's openOutputStream(overwrite:Boolean):OutputStream function, which defines whether to overwrite existing data and returns an OutputStream.
Use this line of code:
var outputStream:OutputStream = resource.openOutputStream(true);
The code of ..., provides a reference to the resource's OutputStream. Finally, the application calls properties.store(outputStream), which stores the symbolsTemp resource that we put away in the last paragraph, then closes the outputStream. Remember always to close any and all OutputStreams that you opened.
Table 4 provides a list of useful variables and functions for the Properties class.
Name |
Description |
|---|---|
get(key:String):String |
Retrieves the value of a name/value pair with the specified name (key) |
load(inputstream:InputStream):Void |
Load the name/value pairs from the passed input stream |
put(key:String, value:String):Void |
Creates the specified name/value pair, or if the pair already exists, updates the value for the name/value pair to the value specified |
store(outputstream:OutputStream):Void |
Stores the properties using the specified output stream |
Obviously, we have to trigger the saveProperties() function at some point. The following two code samples demonstrate saving properties and exiting by using the close() and FX.addShutdownAction() functions.
closeButton = Button {
text: "Exit Program"
action: function():Void {
model.saveProperties();
stage.close();
}
}
FX.addShutdownAction(function():Void {
if (Alert.question("Save your stocks for next time?")) {
model.saveProperties();
}
})
Now that the symbols have been locally stored, they need to be loaded the next time that the user runs the application. For this, a loadProperties() function was created:
public function loadProperties():Void {
println("Storage.list():{Storage.list()}");
entry = Storage {
source: "stockreader.properties"
};
var resource:Resource = entry.resource;
var properties:Properties = new Properties();
try {
var inputStream:InputStream = resource.openInputStream();
properties.load(inputStream);
inputStream.close();
def symbolsTemp = properties.get("symbolsTemp");
if (symbolsTemp != null and symbolsTemp.trim() != "") {
symbols = symbolsTemp.split(",");
}
else {
symbols = [];
}
}
catch (ioe:IOException) {
println("IOException in loadProperties:{ioe}");
}
};
Notice that the beginning of this function is identical to the saveProperties() function. The application still needs a reference to the existing Storage, "stockreader.properties", the resource, and an instance of the Properties class in order to create an InputStream for loading the stored data.
This time, the application will create a variable of type InputStream and assign it the resource's openInputStream() function, which returns an InputStream. It then calls properties.load(inputStream), which loads the name/value pair discussed earlier.
Now that we have the comma-separated list of symbols, symbolsTemp, it's time to split them apart and assign them to the model's symbol variable, which is used by the RSSTasks.
Another important feature of data-storing applications is the ability to clear the user's data. The Storage class provides two functions for clearing the application's stored data: clear() and clearAll(). As their names suggest, clear() deletes a single resource from Storage, and clearAll() deletes all stored items or files.
Following is a clearProperties()function that is called when the user clicks a "Clear Cache" button in the UI, which is shown in Figure 4.

public function clearProperties():Void {
if (entry != null) {
entry.clearAll();
delete symbols;
println("Properties cleared");
}
};
Finally, the following code sample, located in the Main file, shows how the application invokes the loadProperties() function.
function run():Void {
model.loadProperties();
if (sizeof model.symbols > 0) {
model.startFeeds();
}
stage = Stage {
...
...
// ... EXTRA CODE REMOVED
...
...
FX.addShutdownAction(function():Void {
if (Alert.question("Save your stocks for next time?")) {
model.saveProperties();
}
})
}
First, the application loads the stored data. Then, as long as an empty string was not returned and model.symbols actually contains a symbol, the RSSTasks are started and the UI is created, including StockItemNodes for the stored symbols.
The ability to store data locally can benefit the end user in ways other than storing stock symbols. For instance, some applications are designed so that a window opens to the size that it was when the user last closed the application. Storage makes this incredibly simple, as demonstrated in the book Pro JavaFX Platform.
Not only does JavaFX make it easy to invoke RSS feeds, the binding power of the platform easily allows you to update the UI with new information from feeds. Storing local data, an essential feature for most applications, is simple to integrate into your JavaFX code.
The next article in this series will add another UI element to the StockReader example: JavaFX Charts.
We welcome your participation in our community. Please keep your comments civil and on point. You can optionally provide your email address to be notified of repliesyour information is not used for any other purpose. By submitting a comment, you agree to these Terms of Use.
Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.
|
| ||||||||||||