FoneMonkey: The Download

Posted in Stu Stern on December 29th, 2009 by admin
Tonight I am releasing FoneMonkey into the wild. Without further ado, here it is:

FoneMonkey.zip

The iPhone does not yet allow installation of user-defined frameworks, so FoneMonkey is disbributed as a static library along with the image files and nibs required by the FoneMonkey user interface. FoneMonkey.zip contians libFoneMonkey.a as well as a bunch of png and xib files.

The zip file does not include the FoneMonkey source code, which we'll be releasing when we officially launch the FoneMonkey open source project in mid-January, 2010 (ie, just a few weeks from now!). At that time we'll be going live with a community forum as well. Until then, please post questions or comments to this blog.

To get started using FoneMonkey, download the zip file and extract the FoneMonkey distribution folder. Then follow the instructions below.

Happy testing! We look forward to your feedback!

Testing an iPhone application with FoneMonkey

In order to test an iPhone application with FoneMonkey, you must first link FoneMonkey with your application as shown in the 2-minute video below. Step-by-step textual instructions are also provided here.

You might need to view the video in a new window.




  1. Open your application's project in xcode.
  2. Duplicate your application's build target by right-clicking on it and selecting Duplicate from the menu. A new target will be created called YourApp copy.
  3. Rename YourApp copy to something like YourAppTest.
  4. Add the downloaded FoneMonkey folder to your project by right-clicking on the project and selecting Add > Existing Files... from the menu. Navigate to the FoneMonkey folder, select it, and click the Add button.
  5. When the dialog box appears, select the Recursively create groups for any added folders option.
  6. In the Add to Targets box, deselect YourApp and select YourAppTest.
  7. Click Add.
  8. Right-click on the YourAppTest build target and select Get Info from the menu.
  9. On the General tab, delete libFoneMonkey.a from the Linked Libraries. You will need to add CoreGraphics.framework and QuartzCore.framework to the Linked Libraries list they aren't already there.
  10. On the Build tab, scroll down to the Linking section and click on the Other Linker Flags setting. When the dialog box appears, enter:

    -ObjC -lFoneMonkey -LFoneMonkey/lib -all_load

  11. Dismiss the project Info window.
  12. Right-click on YourAppTest build target and select Clean from the menu.
  13. Right-click on YourAppTest build target again and select Build and Debug from the menu.
  14. Your application should start in the simulator. Immediately after it displays, the FoneMonkey console should drop down over it.
  15. See this post for more info about recording and playing back tests with FoneMonkey.

Happy Holidays to You and Yours

Posted in Dave Rodenbaugh on December 25th, 2009 by admin

I’d like to take a minute and wish all the readers of this blog, no matter where they are in the world, or what holidays they celebrate this time of year, a season of peace, happiness and joy.  Enjoy the time with your family and friends.

I hope everyone has a wonderful, prosperous and healthy New Year in 2010.

No related posts.

Google Go: Good For What?

Posted in Dave Rodenbaugh on December 21st, 2009 by admin

My posts on Google’s Go (Part 1 and Part 2) definitely touched a nerve with a few folks.  And I appreciate good dialog on ideas like this…

One pervasive question that I keep hearing is “Who is Go good for?“  And I’m having a hard time finding a good answer.  Even Go’s own FAQ page is uncharacteristically vague about it.

I’d say there are plenty of non-starters to keep Go out of the application programming space.  After my arguments pointing out that it won’t replace Java anytime soon, folks are telling me that I wasn’t looking at the right demographic.  These people suggest that Go is really for systems programmers.  Systems programming has typically been the bastion of C and (more recently) C++ programmers for the past 2 decades.  If you’re doing serious systems programming, you’re in one of those two camps, generally speaking.  Maybe with a touch of assembly here and there.

OK, I’m game for looking at that.  First off, what makes a good systems programming language?  Here are few things we might want:

  1. can operate in resource-constrained environments
  2. is very efficient and has little runtime overhead
  3. has a small runtime library, or none at all
  4. allows for direct and “raw” control over memory access and control flow
  5. lets the programmer write parts of the program directly in assembly language

Does Go really fit into that box?

  1. Go’s performance numbers are rough 6x worse than C++, on average.  The best performing Go test was comparable to the worst C test.  While I gave Go some leniency with Java on performance in an application environment (there are plenty of other non-memory, non-CPU bottlenecks to worry about there), the systems world is far stricter about raw, unabashed execution time and resource consumption.  (+10/20 pts)
  2. Go’s memory and execution footprint are higher than C and C++, according to these stats.  Not exactly an ideal candidate for replacing either of these languages currently entrenched in this space.  An interesting experiment:  Compile Hello World in Go and C++.  Go’s compiled & linked output:  38K, C++ clocks in at 6K, about 84% smaller. (+10/20 pts)
  3. If you include the garbage collector, the Go runtime footprint is certainly larger than C/C++.  But it’s safer than either C/C++ for the same reason.  And to top it off:  Go’s garbage collector isn’t parallel safe right now.  (To be fair, that’s the #1 thing on the TODO list right now for the Go team)  (+15/20 pts)
  4. Raw and direct control is possible, so Go checks in fine here.  You can use this to investigate runtime structures if you like. (+20/20 pts)
  5. This is similar to Java’s native interface (JNI), but statically linked.  So yes, it’s possible. (+20/20 pts)

At 20 pts per question, let’s be kind and give Go a 75/100 possible score there (A solid “C” on the American grading scale, yuck yuck…).  If you’re a C/C++ programmer where you’re already at 100/100 on the above chart, where is your motive to switch here? Couple that with the fact that systems programmers are not exactly known for adopting bleeding edge technology at a rapid pace.  It was years before C++ ever made substantial inroads with the embedded C crowd.  Considering the degree of reliability required to do high quality, bare-metal systems programming, I’d be skeptical of anything new in this space too.

Finally, let’s hit up the syntax argument one more time, because I think this is the crux of the entire problem.  Before I do, let me just say I don’t personally have any problems with Go’s syntax one way or the other.  I’ve learned a plethora of languages in my tenure as a software nerd and adding one more would not be a big deal if I felt the payoff was big enough.  But I think syntax familiarity is a barrier for a lot of people, based on my experience as a language instructor and Parkinson’s Law of Triviality.

Briefly stated, Parkinson’s Law says we unfortunately spend disproportionate amounts of time and energy arguing about things that are more trivial (and we understand) than we do about those that are more substantial (and fail to grasp).  This is particularly true with programming languages and syntax.  I saw that resistance teaching Java to C++ folks back in the mid-90s.  And that wasn’t exactly a big leap.  Changing from C++ to Go is likely to be much worse than C++ to Java, and that resistance is critical to adoption rates.

So I’m not feeling the love for Go replacing C/C++ systems programming either.  If I was looking for a new tool in my toolbox, I don’t think I’d be buying this one from Google.

My new programming tool:  Go!

My new programming tool: Go!

All of this leaves me scratching my head and singing:

Go! Huh!  Yeah!

What is it good for?

Absolutely nothing.

Say it again.”

Related posts:

  1. Google’s Go Isn’t Getting Us Anywhere, Part 1
  2. Google’s Go Isn’t Getting Us Anywhere, Part 2

Episode 22: Sleeping on the Couch

Posted in Drunk On Software on December 20th, 2009 by admin

In this probably worth missing episode, our West Coast Correspondent, Chet Haase joins us in the early hours of the morning to talk about, well, stuff. If you do decide to watch, look for the highlight of the episode, James falling asleep during the show.

Talk Back: How do you test your Flex applications?

Posted in Jon Rose on December 20th, 2009 by jonr

At Gorilla Logic we are proud of using our FlexMonkey tool to test the enterprise Flex applications we are building, but my curiosity has been growing at how others in the community are testing their Flex & AIR applications.

  • What are you using (FlexMonkey, FlexUnit, Mercury QTP, Selenium, RIATest, FunFX, etc)?
  • Have you run into major pitfalls with your Flex testing?
  • Are the tools you are using allowing your team to achieve your testing goals?
  • How would you rate the state of testing in Flex development?

Thanks! I look forward to hearing from you.

LCDS and MySQL Views

Posted in Justin Shacklette on December 18th, 2009 by admin

Once upon a time, in what now seems to be a prior life, I became a published author when my thesis hit the university press. That time has come again. After much effort, the second longest thing I have ever written has been published by InsideRIA. I wrote a two-part article titled Getting Real with LCDS 3 (Part 1 & Part 2) that covers the basics of model driven development with LCDS 3. Yeah me.

More Fun with LCDS

Since I’m such a new-stuff-loving guy I can’t help but return to the backend after a few months on the frontend with various Axiis visualizations (here & here) and a GTween animation (here). So, it’s back to LCDS. Of course, since I last wrote about LCDS, Adobe has shipped LCDS 3, but thankfully they still provide a free developer trial. This time around, I’ll look at LCDS and MySQL Views.

Create Database and Tables

First, let’s set the scene by constructing a new database and some tables. Here’s the database:

CREATE DATABASE football;
CREATE USER 'baller'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON football.* TO baller@localhost;

And here are the tables:

CREATE TABLE teams (
  id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255),
  PRIMARY KEY (id)
);
 
CREATE TABLE players (
  id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(255),
  salary int(11),
  team_id int(11),
  PRIMARY KEY (id)
);
 
CREATE TABLE games (
  id int(11) NOT NULL AUTO_INCREMENT,
  team_id int(11),
  opp_id int(11),
  points int(11),
  win tinyint(1),
  PRIMARY KEY (id)
);

Lastly, we insert some sample data into the database via a script. The final result is our familiar teams-and-players database, but this time with an additional games table.

MySQL Views

A database view is one way to push logic and computations down from the application layer into the database. A judicious use of views can really help keep the application layer nice and clean. But much like database de-normalization, adding views to the database often feels like premature optimization, so be careful. LCDS and the Modeler plugin treat database views just like tables, so everything just works (sort of).

MySQL is a full-featured database that provides a simple CREATE VIEW syntax. For our purposes, a view can be thought of as nothing more than another table that is constructed from other tables via a SELECT statement. Because of this, database views are sometimes called virtual tables. The SELECT statement used to construct the view is often very complicated, involving multiple tables, joins, functions, expressions, and more.

For our example, we will construct two views. First, the team_summary view packages up information from the teams and players tables:

CREATE VIEW team_summary AS SELECT
  t.id,
  t.name,
  COUNT(p.id) AS num_players,
  ROUND(SUM(p.salary)/1000000,2) AS payroll,
  ROUND(AVG(p.salary), 0) AS avg_salary,
  MIN(p.salary) AS lowest_salary,
  MAX(p.salary) AS highest_salary
FROM teams AS t, players AS p WHERE t.id = p.team_id GROUP BY t.id;

The CREATE VIEW above joins the teams and players tables to create seven columns. Here’s a brief description of the generated columns:


 id
team id directly from teams table

 name
team name directly from teams table

 num_players
count up all player ids

 payroll
sum all player salaries

 avg_salary
average all player salaries

 highest_salary
find the maximum player salary

 lowest_salary
find the minimum player salary

Next, we create the game_summary view to collect data from the teams and games tables:

CREATE VIEW game_summary AS SELECT
  t.id,
  t.name,
  CAST(CONCAT(SUM(g.win), '-', COUNT(g.id) - SUM(g.win)) AS CHAR) AS record,
  CAST(SUM(g.points) AS SIGNED) AS points_for
FROM teams AS t, games AS g WHERE t.id = g.team_id GROUP BY t.id;

And a brief description of the generated columns:


 id
team id directly from teams table

 name
team name directly from teams table

 record
concatenate number of wins with number of losses (computed via total games minus wins)

 points_for
sum up all the team’s points for all games

Digging Deeper: The CAST statements are not necessary to construct the game_summary view as far as MySQL is concerned, but I need them to force the correct column types to make LCDS happy.

LCDS 3 and the Modeler Plugin

Now that our database is happily configured, fire up Flash Builder 4 and switch to the Modeler plugin. Create and configure a new LCDS webapp, and then take a look at the RDS Dataview panel:

rds-dataview

You can see both views and tables. Next, simply drag whatever tables or views you want over to the Modeler. Here’s the final model for our sample application:

model
LCDS Modeler Tricks & Tips

There are a couple of tricks to getting views working correctly in LCDS:

  1. Mark each view entity as persistent in the Properties panel (in theory, views can support updates to only those columns that are one-to-one mapped to a table)
  2. Set the id column as the ID property (this is required because views don’t have primary keys, so you have to tell LCDS what to do)
  3. Verify that the database types have correctly come across to LCDS, if not add a CAST to the view column to force the correct type (see table below)

Here’s a simple table showing how the types move from the MySQL database to LCDS, and then from LCDS to PASOs (aka Plain old ActionScript Objects):


 MySQL
LCDS
PASOs

 int
int
int

 bigint
long
int

 decimal
double
Number

 varchar
string
String

Data

We can view the data in the database by using the RDS Query Viewer. Here is the team_summary data:

team-summary

And here is the game_summary data:

game-summary
The App

Alas, since this is an LCDS app, no running demo is possible. Here’s a screenshot instead, showing a selected team and the associated team & game summary data.

screenshot

Our sample app closely follows the plan I set forward in Getting Real with LCDS, Part 2. We define our services and associated CallResponder’s in MXML, and event handlers in ActionScript.

Here is the relevant section of code showing the various event handlers:

private function teamChangeHandler():void {
	var teamId:int = list.selectedItem.id
	getTeamSummary.token = teamSummaryService.getById(teamId);
	getGameSummary.token = gameSummaryService.getById(teamId);
}
 
private function getTeamSummaryResult(event:ResultEvent):void {
	var summary:TeamSummary = (event.result as IList).getItemAt(0) as TeamSummary;
	numPlayers.text = summary.numPlayers.toString();
	payroll.text = cf2.format(summary.payroll) + ' million';
	avgSalary.text = cf.format(summary.avgSalary);
	lowestSalary.text = cf.format(summary.lowestSalary);
	highestSalary.text = cf.format(summary.highestSalary);
}
 
private function getGameSummaryResult(event:ResultEvent):void {
	var summary:GameSummary = (event.result as IList).getItemAt(0) as GameSummary;
	record.text = summary.record;
	pointsFor.text = summary.pointsFor.toString();
}

And here is the MXML Declarations block:

<fx:Declarations>
	<s:CallResponder id="getAllTeams" />
	<s:CallResponder id="getTeamSummary" result="getTeamSummaryResult(event)" />
	<s:CallResponder id="getGameSummary" result="getGameSummaryResult(event)" />
 
	<football:TeamService id="teamService" />
	<football:TeamSummaryService id="teamSummaryService" />
	<football:GameSummaryService id="gameSummaryService" />
</fx:Declarations>

The application is very simple. When a user clicks on a team, the teamChangeHandler() is called. The handler queries the LCDS backend and sets the CallResponder’s token property. When the asynchronous backend eventually returns some data, the data is forwarded to the appropriate result handler. In the result handler, the incoming PASO has it’s properties are formatted and then assigned to the matching form element for display.

Files

Military Software Sucks

Posted in Dave Rodenbaugh on December 17th, 2009 by admin

Apparently the US Military can’t write software worth a damn.  Here’s a textbook-classic case of what happens when you decide to ignore a problem that is clearly evident at requirements time until well after post-deployment.

The Wall Street Journal did an article about the unmanned drones zipping over Afghanistan and Pakistan.  Apparently, local insurgents found a $26 piece of off-the-shelf software that could tap into the drone’s unencrypted video feeds and give the insurgents a clear view into what the US Military was watching, thus ruining the element of surprise.

Can you say “Ouch”?

A quote from the article itself says it all about military incompetence arrogance:

The potential drone vulnerability lies in an unencrypted downlink between the unmanned craft and ground control. The U.S. government has known about the flaw since the U.S. campaign in Bosnia in the 1990s, current and former officials said. But the Pentagon assumed local adversaries wouldn’t know how to exploit it, the officials said.

Holy Ostrich-Heads-In-The-Sand, Batman!  Not only did the military put software out the door with an obvious security flaw in it, they’ve ignored this problem for over 10 years because they thought the enemy was too dumb to figure it out! And the justification?

Fixing the security gap would have caused delays, according to current and former military officials. It would have added to the Predator’s price.

Yes, that’s absolutely trueBut honestly, how much would it really add? The Predators already run in the millions per drone (10-12 per the article).  Let’s analyze that, based on current prices of software contracting, estimated efforts and the technology involved.  First, we need a list of assumptions:

  1. Encryption requires additional processing power to encrypt at the drone and decrypt at the receiver.  Let’s assume they add a special card to each drone to dedicate to this task so the video feed isn’t compromised on the sending end.  Cost:  $1,000 per drone because it’s a special piece of hardware capable of running at 2Gs.  (Off the shelf solution today:  probably about $250)
  2. Cost to install in each drone:  Let’s say that it takes a tech about 2 hours worth of time per drone.  And assume the tech is paid a modest $20/hour to do his work.  $40 per drone.
  3. The card requires additional software to link it into the current drone video processing loops.  Let’s assume the video processing is well-known, and the encryption addition takes roughly 2 engineers 1 month to complete.  (2 engineer months @ $150/hour government contracting rates = $24,000 for all drones).
  4. The receiver software requires a comparable upgrade to handle the decryption.  Assume another 2 engineers are dedicated to that task for a similar length of time.  Another $24,000 for all drones.
  5. Figure in some extensive testing:  Another 2 engineers for a month:  $24,000 for all drones.
  6. Assume that managers are involved and their costs are amortized into other projects, which is likely true.
  7. Finally, assume this is for an existing fleet of 1,000 drones.

Adding all that up, I get the following:

  • 1,000 drones * $1,040 = $1.04 million for all drones.
  • Fixed costs = $72,000
  • Total costs = $1,112,000 dollars for 1,000 drones OR
  • $1,112/drone

At $10 million dollars (the low end) per drone, that’s a 0.0112% increase in price per drone.  Hardly a massive cost overrun by military standards.  And let’s assume I’m off by a factor of 10 on all my calculations…still, that’s still about 0.11%.  Again, not a massive overrun for something that mission critical.  Compared to most software projects with mid-double digit overruns on developer time, this is positively amazing.

And the delay argument?  Maybe 6 months to retrofit the fleet.  At best.  You’d think that in 10 years time, the military could find 6 lousy months to upgrade its most important asset in the 21st century.  Even a phased upgrade would have worked here over that time frame.

This is all taking into account that the military is fixing this problem well after the design and implementation phases (our old friend Habit 5:  Fix it Later) instead of identifying and fixing this problem up front.  That would reduce the costs even further.  I find it completely incredulous that not a single person during the design or requirements gathering phases said, “Hey, maybe we ought to encrypt the video feed…”  Aren’t they supposed to gather information, uh, secretly?

If you think the software we write is bad, wait until you see our solutions!

If you think the software we write is bad, wait until you see our solutions!

Clearly one of two things is going on here:

  • The military is too lazy or stupid to realize that the enemy will find and crack that exploit given enough time and resources (let’s just throw out the number 10 years…)
  • The military price to fix this flaw is much higher, meaning that the cost overruns are due to corruption, incompetence, or outright greed in government contracting.

Shame on everyone involved.  This sort of breech wouldn’t happen at Amazon.com’s ecommerce site.  It shouldn’t happen with some of our most important software technology given that this is a solvable problem with known constraints.

* UPDATE @ 12:48p, 12-17-2009:  My math was off by a factor of 1,000 on the calculations and my addition sucked.  I’ve just embarrassed every math teacher I’ve ever had.  Now it’s even cheaper and more horrific!

No related posts.

FoneMonkey: The Movie

Posted in Stu Stern on December 16th, 2009 by admin
In this video, recently smuggled from Gorilla Logic labs deep beneath the surface of the Earth, we see the revolutionary new open source, iPhone application testing tool FoneMonkey recording and playing back interactions with an application that was cobbled together from Apple's UICatalog and GLPaint sample applications (which together are comprised of most of the iPhone SDK UI Components).

The video demonstrates FoneMonkey recording and playing back various actions including button touches, table scrolling and selection, switches, sliders, text entry, tab selection, finger dragging (in this case, painting), and even phone shaking (which is the gesture that causes GLPaint to erase the current painting).

You might need to view this video in a new window.



As you can see, the monkey can really dance. I hope to post the library binary and installation instructions within the next few days.

Episode 21: Ralph Hauwert co-founder PaperVision3d

Posted in Drunk On Software on December 16th, 2009 by admin

In this episode we enjoy scotch with Ralph Hauwert, one of the founders of the popular PaperVision3d framework.

Read Ralph’s blog at: http://www.unitzeroone.com/blog/
Learn more about the PaperVision3d project at: http://papervision3d.org/

Custom JSF Components with Facelets

Posted in Bob Hedlund on December 15th, 2009 by admin
The Overview
Creating custom components can really clean up your faces pages and keep you from repeating yourself over and over. And its fun to do! There are a number of posts out there and tutorials about creating custom components, and often times they leave out the most important pieces of configuration. In this post I will specify how to create composite components with Facelets. This is different from the jsf style custom components in that these are much easier to create, require much less configuration, and often times solve the problem at hand very quickly.

The Affected files
  1. the Web Descriptor
  2. The component definition
  3. The tag library descriptor
  4. The pages that you insert the component into


The Web descriptor, (web.xml) tells your container about your tag lib descriptor file.

The component definition fie specifies how the component is laid out. It may have logic in it and you may pass parameters to it. It produces the markup that appears in your faces page.

The Tag Library descriptor is an xml file that describes to the container what to do when it encounters your custom component tag.

Finally, your pages need to include the tag for it to be seen.

Getting Started
The first thing to do when creating a component is to generate the markup, styling and any scripts that accompany it in a sandbox. Try inserting it in your pages and seeing what it looks like and get it to work correctly. Once this is done, you are ready to generate the reusable tag that will display your component each time you need it. In this case I made a progress bar for my application.

Directory Layout
Under WEB-INF, create a directory called facelets, with a sub directory called tags.
In the tags directory we are going to place our tag library decriptor and our component.

myProject.taglib.xml
The Tag Lib Descriptor file: (myProject.taglib.xml)

<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://www.yourId/jsf</namespace>
<tag>
<tag-name>progress</tag-name>
<source>progress.xhtml</source>
</tag>
</facelet-taglib>



The file specifies the following items:

namespace - Your unique id that wont conflict with other known urls. It does not need to exist on a server, it just needs to be unique. In this example I called it http://yourId/jsf. Any components that you place in this tag lib descriptor will be accessed in you pages using this as the uri to find this definition file. So in your pages that contain the component, the following tag will be added in your opening definitions:
xmlns:xx="http://www.yourId/jsf"
where xx will be the tag handle in the page.

tag-name is the name of this particular component. In this case it is a progress bar. In your page you will access it via xx:progress

source is the name of the file that we will create to define our tag - the markup.


web.xml
Now we need to tell the application about our library. In web.xml, create the following entry:

<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/WEB-INF/facelets/tags/myProject.taglib.xml</param-value>
</context-param>

If you already have an entry like this, the param values can be a semi-colon delimited list.


progress.xhtml
So now we have our tag lib descriptor, and the container knows about it. Lets generate the component file. The name of the file must match the name in the taglib descriptor for the component. It is the source tag. So when the application encounters the tag name in the page, it looks up in the taglib descriptor (which it gets from the namespace definition) and finds the source file associated with the tag name.

This file also resides in WEB-IN/facelets/tags.

The file:



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fn="http://java.sun.com/jsp/jstl/functions">

<ui:composition>

<span class="progressContainer" >
<div class="progressBar" >

<c:if>
...more markup
</c:if>

</div>
</span>

</ui:composition>
</html>




I've left out my markup, but you can put yours in. We will look at parameter passing a bit later. For now, lets just get you using a tag in your pages. At this point everything is set to go. You just need to include the definition for your library in your page, and then specify the tag in the page.

So in your page:

In the opening tag, include the definition:

xmlns:xx="http://www.yourId/jsf"

Note that this must match the namespace specified in your tag lib descriptor. Remember that this is an arbitrary name for id purposes only. The xx is an arbitrary handle for accesing the library in your page.

Then, in your page to use the tag you would call:


<xx:progress/>



OK So at this point the tag should appear in your pages where you specify it. In the next edition, we will discuss parameter passing and using jstl in the tags. I also like to include certain tags in my templates that pages use. This requires a different approach for parameter passing, as the template defines where the tag will be, but the pages may want to use the tag differently.

Hope that helps.