The Steam language is a powerful component of the Locomotive which allows you to generate Web pages with dynamic, context-dependent content. The Steam language is embedded directly into HTML documents. The Locomotive displays these documents by executing the Steam commands and inserting their results directly into the HTML document, thereby creating a custom Web page, which is then passed back to the Web user. Almost any kind of information from the Locomotive or the database can thus be inserted into a Web page. Not only that, but the Steam language has a sophisticated set of features for configuring Web pages, such as if-then-else conditions, variables, arithmetic and logical operators, and inserting one document into another document.
All Steam language commands are enclosed in square brackets, [ and ]. (To insert the bracket characters themselves into a document, you say \[ or \].) Most Steam commands, when executed, become text which can be inserted into a document. The simplest Steam command is the insertion of a variable into a document:
Let's take an example. Let's say that you have the following HTML document:
<HTML> <HEAD><TITLE> My Home Page </TITLE></HEAD> <BODY> Hi, this is [USERNAME]'s home page. </BODY> </HTML>
Now, let's say that the Locomotive knows that the value of the variable USERNAME is "Audrey". Then, the Locomotive will insert "Audrey" where it sees [USERNAME], and it will send back the following page:
<HTML> <HEAD><TITLE> My Home Page </TITLE></HEAD> <BODY> Hi, this is Audrey's home page. </BODY> </HTML>
And now you know how to use the most common Steam command! This first step, knowing how to insert a variable into a Web document, will get you a long ways towards creating dynamic Web pages, even before you learn any other Steam command. Now, what if the variable USERNAME was not defined anywhere in your java module? In that case, no text will be inserted in the place of [USERNAME], and you would see the following instead:
<HTML> <HEAD><TITLE> My Home Page </TITLE></HEAD> <BODY> Hi, this is 's home page. </BODY> </HTML>
(By the way, variable names in Steam can include the underscore character, '_', for example, TIME_LOGGED_IN.) Now, you might wonder: How does the Locomotive know what the value of USERNAME is? There are two ways you can tell the Locomotive about the USERNAME variable. One, you can write a Java module which plugs into the Locomotive, and you can give a value to USERNAME by calling a Java function. Two, you can use the following Steam command inside your HTML document:
This command means, "assign the value of what is within the [quote ...] command to the variable called my-variable." (This command must precede the use of the variable my-variable in your document.) The [quote...] command allows you to create a block of text which can be inserted into the document or into a variable. Let's take an example:
[set MESSAGE [quote The Perfect is the enemy of the Good.]] Message of the day: <b> [MESSAGE] </b>
Would produce:
Note that the [set...] command itself does not insert any text into the document. Its only effect is to assign some text to the variable called MESSAGE. Note also that you can assign any text, with as many words as you like, to a variable using [set... [quote...]]. In fact, your text can even include other Steam commands, such as variables. For example, if you say [set HOME_PAGE [quote [USERNAME]'s Home Page]], where USERNAME contains "Katherine", then HOME_PAGE will contain "Katherine's Home Page". Later on, we'll combine [set...] with other Steam commands to produce some more sophisticated results.Message of the day: <b> The Perfect is the enemy of the Good. </b>
In practice, the Locomotive and the modules that you write will set most of the variables that are used in a Steam document. Variables which have values, but are not referred to in a document, are simply ignored.
A surprising benefit of the [set...] command is that it allows the author of a document to pass information into the Locomotive while it is running. For example, let's say that the module you wrote needs to know who was the last person to modify a document. An easy solution is to set a variable called LAST_AUTHOR within your document:
When the Locomotive reads your document and encounters this command, it will create the variable LAST_AUTHOR and give it a value of "Peter". If LAST_AUTHOR is already a known variable, it will be given a new value of "Peter".[set LAST_AUTHOR [quote Peter]]
As you can imagine, the [set...] command can be used to set or change configuration parameters during execution time, without needing to rewrite your program.
Now that we've covered some examples of Steam commands, we're ready to give an overview of all Steam commands. This section lists each command and a short description of it. It can be used as a quick reference guide. The section after this one will go more into detail about each command.
The Steam language has an easy format. Except for [] and [variable], all the commands follow this pattern:
where any of expression-1 ... expression-n may be a number, a variable name, a block of text, or a Steam command.
Here is a list of all the Steam commands.
[] | This Steam command simply inserts empty text. It is usually combined with other Steam commands. |
\[, \], or \(any character) | The backslash character, '\', tells Steam to interpret the next letter, no matter what it is, as text, not as a part of a Steam command. |
[variable] | Inserts the value of variable into the text. If variable is not defined, inserts nothing. Note: variable should be a word that starts with an alphabetical letter and contains only alphabetical letters, numeric digits, or the underscore character, '_'. In this Steam user manual, my variable names are all capital letters and underscores, but that's just my own style. |
[set variable value] | Gives a variable a value. This command does not insert any text into a document; its only effect is to set the value of a variable. If you wish to take away a variable's value, then you can set that variable's value to empty text, like this: [set my_variable[]]. |
[if [condition] [do-this-if-true] ] [if [condition] [do-this-if-true] [do-this-if-false] ] |
If the Steam command condition results in non-empty text, then the Steam command do-this-if-true will be executed, and its result will be inserted into the document. However, if condition results in empty text, then do-this-if-false will be executed instead, and its result will be inserted. If there is no do-this-if-false part, then empty text will be inserted. |
[quote text-with-steam] | text-with-steam contains text of unlimited length that may contain Steam commands. The Steam commands are executed and inserted into the text, and the result is inserted directly into the document. Except for the [loop...] and [table...] commands (see below), [quote...] is the ONLY Steam command which allows you to insert blocks of literal text and/or a series of other Steam commands. Therefore, it can also be used to group lots of Steam commands into one block, which can be used within [if ...] commands. |
[load document-full-path] | This inserts the contents of a document, located at document-root/document-full-path, into the current document. (You specify document-root in the Locomotive configuration file) |
[load document-full-path noeval] | This inserts the contents of the document at document-root/document-full-path into the current document, but without interpreting anything between square brackets, [...], as Steam commands. |
In the following mathematical and logical operations, numeric-expr can be either a number (such as 25), a variable name (such as NUM_USERS), or a Steam command.
[+
numeric-expr-1 numeric-expr-2 ... numeric-expr-n] [- numeric-expr-1 numeric-expr-2 ... numeric-expr-n] [* numeric-expr-1 numeric-expr-2 ... numeric-expr-n] [/ numeric-expr-1 numeric-expr-2 ... numeric-expr-n] |
These are arithmetic operators which operate on all the numeric-exprs which follow them. The result is inserted as text. For '-', you subtract numeric-expr-2, ... numeric-expr-n from numeric-expr-1 to get the result. For '/', you divide numeric-expr-1 by each of numeric-expr-2, ... numeric-expr-n to get the result. You need at least two numeric-exprs for these operations, but you can have as many as you want. |
[=
expression-1 expression-2 ... expression-n] [eq expression-1 expression-2 ... expression-n] [neq expression-1 expression-2 ... expression-n] [> numeric-expr-1 numeric-expr-2] [>= numeric-expr-1 numeric-expr-2] [< numeric-expr-1 numeric-expr-2] [<= numeric-expr-1 numeric-expr-2] |
These are comparison operators which operate on numeric-exprs. neq, eq, and = also operate on text. (eq and = are the same operator; choose your preference) If the comparison is true, then the word "true" is inserted, else no text is inserted. These are usually used as the condition in the [if...] command. neq is the same as [not [eq...] ]. |
[AND
expression-1 expression-2 ... expression-n] [OR expression-1 expression-2 ... expression-n] [NOT expression-1 expression-2 ... expression-n] |
These are logical operators which combine the results of comparisons. AND inserts "true" if all the expressions are non-empty text. OR inserts "true" if any of them are not empty. NOT inserts "true" only if all of the expressions are empty text. |
The following commands operate on arrays, tables, or iterating over a loop:
[colname array-variable index-of-an-item] |
If you have defined an array within your java module in the Locomotive, then you can access any single item within that array using this command. |
[colname array-variable] |
This command is a variant of the previous command, except that this command is used only within [loop...] and [table...] commands. This command is equivalent to: [colname array-variable [loop_index]]. |
[loop number-of-times text-to-repeat] |
This command allows you to insert text-to-repeat (text which may contain Steam commands) for number-of-times times. Within this command, you can use either form of the [colname...] command. While iterating through a [loop...] command, the Steam variable loop_index is automatically set to the current loop counter, starting at 0. |
[table table-variable text-to-repeat] |
If you have defined a SortableTable within your java module, then you can use this command to iterate over each row of your SortableTable and insert text-to-repeat (text which may contain Steam commands) for each row. Within this command, you can use either form of the [colname...] command. While iterating through a [table...] command, the Steam variable loop_index is automatically set to the current row counter, starting at 0. During iteration, you can refer to the item in the table at: (row = loop_index, column = your-colname) within your SortableTable by saying [colname your-colname]. |
Command: | [if [condition] [do-this-if-true] ] [if [condition] [do-this-if-true] [ do-this-if-false] ] |
The [if...] command allows you to insert different text depending on some condition you specify. It behaves in the same way as an if-then or an if-then-else construct in most programming languages. That are two variations of [if...] commands, the if-then form and the if-then-else form. Here is an example of the if-then form:
[if [USERNAME] [quote Hi, [USERNAME]!]]
If USERNAME = "Max", then the above statement would be interpreted as:
[if Max [quote Hi, Max!]]
Because the condition, [USERNAME], is "Max", a non-empty text, the do-this-if-true part is executed, and you would see:
Hi, Max!
The [quote...] command is often used in combination with [if...] commands, to insert blocks of text if a condition is true or false. Notice how [USERNAME] was part of the [quote...] command. You can put Steam commands within Steam commands, within Steam commands, etc. -- there is no limit on the depth of containment. (Just don't forget that for every open bracket [, you need a closing bracket ]!)
Now, let's look at an example of the if-then-else form:
[if [> NUMBER_OF_USERS 10] [set NUMBER_OF_USERS 0] [set NUMBER_OF_USERS [+ 1 NUMBER_OF_USERS]] ]
In this example, the [if ...] command spans several lines. In fact, any Steam command can span any number of lines. The purpose of the [if...] statement is to increase the value of NUMBER_OF_USERS by one. However, if NUMBER_OF_USERS is greater than 10, then we want to reset it to zero. Notice that in the statement:
[set NUMBER_OF_USERS [+ 1 NUMBER_OF_USERS]]
NUMBER_OF_USERS is being assigned a value which depends on the value of NUMBER_OF_USERS itself. Don't worry, this is okay. It acts the same as this common statement in most programming languages:
NUMBER_OF_USERS = NUMBER_OF_USERS + 1.
What if NUMBER_OF_USERS were not a number? This is an interesting question. If NUMBER_OF_USERS did not exist at all, or NUMBER_OF_USERS contained text which was not a number, then in all arithmetic operations, it would be treated as if it had a value of zero. However, all comparison operations involving NUMBER_OF_USERS would return false. In other words, make sure that NUMBER_OF_USERS is a number before using it in a context requiring a number.
Now, we're going to do something slightly funky and show you a different way to say the previous command. Hopefully, this will show you how flexible Steam can be, and also bend your mind a little bit! Let's have some fun.
[set NUMBER_OF_USERS [if [> NUMBER_OF_USERS 10] 0 [+ 1 NUMBER_OF_USERS] ] ]
If you think about it, this accomplishes exactly the same thing. If NUMBER_OF_USERS is greater than 10, then the [if...] will return zero, else it will return NUMBER_OF_USERS + 1. Notice that we did not have to put '0' around brackets, [ ]. That's because it's just a number. In fact, if we had said [0] instead, then the Locomotive would look for a variable named '0' -- which probably does not exist!
Command: | [quote text-with-steam] |
Command: | [load document-full-path] |
[load admin/users/edit-users.steam]
If you specified a document root of "/usr/local/Locomotive" in your Locomotive configuration file, then this command will read and insert the contents of the document residing at: /usr/local/Locomotive/admin/users/edit-users.steam. If that document has [set...] commands which assign values to some variables, then, after the [load ...] command, those variables will now contain those values. In other words, the Locomotive treats the current document exactly the same as if it contained the loaded document.
Command: | [load document-full-path noeval] |
[set SECONDS_SINCE_MIDNIGHT [+ [* 60 60 HOUR] [* 60 MINUTES] ] ]
For this example, let's say that HOUR = 9 and MINUTES = 45. Then, SECONDS_SINCE_MIDNIGHT would get the value (9 * 60 * 60) + (45 * 60), or 35100.
Note that you can use the names of variables without using brackets, [ ], within arithmetic Steam commands, whereas you cannot do this inside other Steam commands, such as [set...] or [quote...]. For example, if you said: [set MY_NAME USERNAME], then MY_NAME would get the value of the text, "USERNAME" -- it would NOT get the value within the variable USERNAME unless you said: [set MY_NAME [USERNAME]]. The reason for this difference is because [set...] is more often used to assign blocks of text to a variable, and seldom used to assign one variable to another, while arithmetic and logical operators most often operate on single variables. Keep this difference in mind.
= or eq | tells whether a number of expressions evaluate to the same result. (Note: = and eq can operate on numbers, text, or Steam commands.) |
neq | tells whether at least one expression among a number of expressions evaluates to a different result from all the other expressions. Equivalent to [not [eq...]]. Can operate on numbers, text, or Steam commands. |
> | tells whether the first numeric expression is greater than the second. |
>= | tells whether the first numeric expression is greater than or equal to the second. |
< | tells whether the first numeric expression is less than the second. |
<= | tells whether the first numeric expression is less than or equal to the second. |
[if [= USERNAME FIRST_NAME EMAIL_NAME] [quote Your username is the same as your first name and email name.]] [if [neq FIRST_NAME EMAIL_NAME] [set USERNAME EMAIL_NAME]] [if [neq USERNAME FIRST_NAME EMAIL_NAME] [quote Among your username, first name, and email name, one of these is different from the others.]] [if [> NUMBER_OF_USERS 10] [set NUMBER_OF_USERS 0]]
In the last example, what if NUMBER_OF_USERS were not a number? This is an interesting question. If NUMBER_OF_USERS did not exist at all, or NUMBER_OF_USERS contained text which was not a number, then in all arithmetic operations, it would be treated as if it had a value of zero. However, all comparison operations involving NUMBER_OF_USERS would return false. In other words, make sure that NUMBER_OF_USERS is a number before using it in a context requiring a number.
A simple way to test whether NUMBER_OF_USERS is a number is the following:
[if [>= NUMBER_OF_USERS NUMBER_OF_USERS] [quote The variable NUMBER_OF_USERS is a number with the value [NUMBER_OF_USERS].]]
This test works because if NUMBER_OF_USERS is a number, then [>= NUMBER_OF_USERS NUMBER_OF_USERS] will always return true, because any number is greater than or equal to itself. However, if it is not a number, then any arithmetic comparison with it will return false. (Note: we can't use '=' in place of '>=', because '=' can compare text expressions.)
Of course, if you don't know whether NUMBER_OF_USERS is even a variable containing a value, you can simply say:
[if [NUMBER_OF_USERS] [quote The variable NUMBER_OF_USERS contains the value [NUMBER_OF_USERS].]]
Command: | [AND expression-1 expression-2 ... expression-n] [OR expression-1 expression-2 ... expression-n] [NOT expression-1 expression-2 ... expression-n] |
There is no limit on the number of expressions that can be included inside each command. Here are some examples:
[if [AND [>= NUMBER_OF_USERS MIN_NUMBER_OF_USERS] [<= NUMBER_OF_USERS MAX_NUMBER_OF_USERS] [USERNAME] [quote Congratulations, [USERNAME], you can log on.]]
Notice that [USERNAME] was one of the expressions in the [AND...] command. In that context, the [AND...] command will treat [USERNAME] as a "true" statement if the variable USERNAME has a non-empty text value; if not, then the entire [AND...] command will return a "false" result -- in other words, empty text.
[if [NOT [USERNAME] [FIRST_NAME] [LAST_NAME]] [quote Login Name: <INPUT NAME="USERNAME" LENGTH=20>]]
In this example, if none of the variables USERNAME, FIRST_NAME, or LAST_NAME has a value, then an HTML text field will be displayed.
The [NOT ...] operator is useful in combination with the [= ...] operator to determine whether a group of expressions all evaluate to the same text or not:
[if [NOT [= USERNAME LOGIN_NAME FIRST_NAME]] [quote [set USERNAME [FIRST_NAME]] [set LOGIN_NAME [FIRST_NAME]] ] ]
In that last example, we used the [quote ...] command not to insert any text, but rather, to be able to run two commands within one Steam command.
By the way, you can "nest" these commands within each other, just like any other Steam Command:
[if [OR [AND [= SOCRATES MAN] [= MAN MORTAL]] [SOCRATES_DIED] [NOT SOCRATES_LIVES] ][quote I'm sorry, Socrates is mortal.] ]
This command allows you to get the value of any item in an array which you have defined in your Java module. Let's take an example. Let's say that you've defined an array of Strings called "usernames" in your Java module:
String usernames[] = new String[4]; usernames [0] = "Matthew"; usernames [1] = "Mark"; usernames [2] = "John"; usernames [3] = "Ringo"; ... hd.subs.put ("usernames", usernames);
Then, within your Steam document, [colname usernames 2] will generate "John". Notice that you can use either a number or a Steam command for the index argument. For example, let's say that the variable WHICH_USER has the value "3". Then, [colname usernames [WHICH_USER]] will generate "Ringo". However, note that you must specify the name of the array directly; you cannot use a Steam command to generate it. For example, you can't say [colname [WHICH_ARRAY] 3].
This command is a variant of the previous command, except that this command is used only within [loop...] and [table...] commands. Its index argument is replaced by the value of the variable loop_index that is set during each cycle of the [loop...] or [table...] command. In other words, this command is equivalent to:
[colname array-variable [loop_index]].
There are examples in the [loop...] and [table...] commands.
Sometimes, you may want to repeat some text or Steam commands a number of times in your Steam document. This command allows you to do that. The argument, number-of-times, can be either a number or a Steam command which generates a number. The argument, text-to-insert-each-time, is text interspersed with any number of Steam commands, including either form of the [colname...] command. While iterating through a [loop...] command, the Steam variable loop_index is automatically set to the current loop counter, starting at 0. Here's an example of this command involving loop_index:
[loop 4 Counter = [loop_index]. ]
Which generates:
Counter = 0. Counter = 1. Counter = 2. Counter = 3.
Here's an example which includes the special use of the [colname column_name] Command:
[loop [NUMBER_OF_USERS] The user at [loop_index] is named "[colname usernames]".<BR> ]
which generates:
The user at 0 is named "Mark".<BR> The user at 1 is named "Matthew".<BR> The user at 2 is named "John".<BR> The user at 3 is named "Ringo".<BR>
Recently, we added some very powerful functionality to the Locomotive. This command is one which gives us pride. In the Locomotive, there is a Java class called SortableTable which can store the results of an SQL query in a table that can be sorted. SortableTable gives you the ability to customize the way a database table looks in your document. If a SortableTable (see SortableTable.java) is defined within your java module, then you can name it as the first argument in the [table ...] command to iterate over each row of that SortableTable and insert some text (which may include Steam commands) for each row.
Within this command, you can use either form of the [colname...] command. While iterating through a [table...] command, the Steam variable loop_index is automatically set to the current row counter, starting at 0. During iteration, you can refer to the item in the table at: (row = loop_index, column = your-column) within your SortableTable by saying: [colname your-column], or by saying: [colname your-column [loop_index]].
The [table ...] command is just like a [loop...] command that has several [colname...] commands within it, except that all the names of the columns belong to a single SortableTable. Let's take an example.
Let's say that you've defined a SortableTable in your Java module called "users_messages". This SortableTable has two columns named "usernames" and "number_of_messages". The contents of this table are:
row: | usernames: | number_of_messages: |
---|---|---|
0 | Sterling | 54 |
1 | Ben | 23 |
2 | Chris | 12 |
3 | Thede | 35 |
Now, consider the following block of Steam within a document:
<table> [table users_messages <TR><TD> The user at row [loop_index] is named "[colname usernames]".</TD> <TD> [colname usernames] has [if [>= [colname number_of_messages [loop_index]] [colname number_of_messages 3]] [quote as many or more] [quote fewer] ] messages than [colname usernames 3].</TD></TR> ] </table>
When this block is interpreted, it will generate a table which looks like:
The user at row 0 is named "Sterling". | Sterling has as many or more messages than Thede. |
The user at row 1 is named "Ben". | Ben has fewer messages than Thede. |
The user at row 2 is named "Chris". | Chris has fewer messages than Thede. |
The user at row 3 is named "Thede". | Thede has as many or more messages than Thede. |
Here are some things to take note of in this example. First, note that [colname usernames] means, "insert the value of the column named 'usernames' at the row loop_index, from the current table". If there is already an array named "usernames", that array will not be used; this "usernames" refers to the column in the users_messages SortableTable. Because of this behavior, be careful in naming your arrays and columns, so you don't get confused.
Second, note that we are allowed to use the "absolute location" form of the [colname...] command within the [table...] command. That is, [colname usernames 3] refers to the value at the column "usernames" at row 3. This feature therefore allows you to use the value of the previous or next item in the column, by saying [colname usernames [- loop_index 1]] or [colname usernames [+ loop index 1]]. This gives you great flexibility in referring to other items within the table.
Here are a few tips about how you can test and debug your HTML templates while you're testing your features.
LOCO_CHECK_NEW_TEMPLATE_FREQ 1When you run a Locomotive on the live site, you can make this value higher, in order to save time, because you know that the templates won't change very often on the live site. (There is also a way to force the Locomotive to reload all templates once, whether they've changed or not; see The Locomotive Administrators Guide.)
[if [and [USERNAME] [= USERS_AGE 21]] [quote Hi, [USERNAME], you're legal!! :^)]]You can check whether USERS_AGE = 21 directly by saying [= USERS_AGE 21], which will evaluate to "true" if it is true. Better yet, we can simply find out the value of the USERS_AGE variable by saying [USERS_AGE].
The Locomotive defines the following variables, which you can use in any of your Steam documents by calling the StandardInsertions.insertAll() command:
DATE | The date today, in the form: Mmm dd, yyyy. For example: Mar 23, 1998. |
TIME | The time right now, in the form: hh:mm AM/PM. For example: 9:45 P.M. |
USERNAME | If the user has logged in with a password, this variable will contain his or her username; otherwise, this variable is not set. |
USERID | Each user in the Locomotive system has a unique numeric userid. If the user has logged in, then this is their userid. Otherwise, if the user is anonymous, this is 0. |
SESSIONID | The Locomotive uses browser cookies to track a user's single, continuous use, or "session". This variable refers to the current session's ID. |
LOCOMOTIVE_SERVER_VERSION | This is the version number of the current Locomotive, which is useful for things like tracking bugs. |
SYSTEM_TAG | This is the main Locomotive identification tag. It's pulled from the SYSTEM_TAG config variable set in the loco.conf file. |
SYSTEM_INSTANCE_ID | This is the unique ID associated with this particular Locomotive. Taken from the config variable SYSTEM_INSTANCE_ID, set in loco.conf |