(note: this is the beginnings of a new tutorial. The old tutorial is still available here.)
Before you use this tutorial, you should have IOWA installed and running on your machine. The current IOWA distribution is available from http://beta4.com/iowa/#download, and installation instructions are provided in the README file. Once you have successfully tested the guestbook or another example application, proceed with the tutorial.
This tutorial assumes some basic familiarity both with HTML and with the Ruby programming language; for more info, see http://www.ruby-lang.org.
Make a new directory for your tutorial files. We'll start by creating a single file: "Main.html". All IOWA applications start at the Main page.
For now, we'll just put some html in the file to make sure everything's working. For example, type this into Main.html:
<html> <body> <h1>Hello World!</h1> </body> </html>
Now open a new shell and run the following command:
ruby -riowa -e "Iowa.run('tutorial')"
This should start up an IOWA application from the current directory, and name it 'tutorial'. (Don't close this shell; just leave it running throughout the rest of the tutorial). If you go now to http://localhost/iowa/tutorial, you should see Hello World! in large type.
So far, not very exciting. Let's make this a little more dynamic:
At the top of your html file, add the following code:
<% class Main < Iowa::Component def time Time.now end end %>
This is the Ruby block of an IOWA file; it encapsulates all of the logic for the page in a single class definition. The name of the class should be the same as the name of the html file - in this case, Main.
Somewhere in the html, add something like this:
The current time is <b>@time</b>.
Hit reload in your browser. IOWA will notice that Main.html has been modified, reevaulate it, and show you the modified page - with the current time. Hit reload a few more times to make sure the time is updating correctly.
Not exactly earth-shattering yet. Still, a tiny beginning of the design principles of IOWA are showing themselves: except for the single Ruby block at the top of the file, the html template is completely clean of custom tags or code; the Ruby code is equally clean of any html. The identifier @time in the template is completely abstracted from its implementation in the code; the programmer could change the implementation (for example, to Time.now.gmtime), or the designer could change the presentation (for example, by changing the <b> tags to <i> tags) without affecting each other in the slightest.
As we move on to more complex applications, the power of these principles will become increasingly evident.
Let's extend our little Hello World application to use one of the most common features of dynamic webpages: displaying changing lists of items. We'll add a "Log" link to the page that'll add the current time to a stored array of times, and then show that array as a bulleted list.
Adding a link is easy: we just insert the following into the html:
<a>Log</a> the current time.
But we haven't given it an href. What should it be?
Links within IOWA apps don't have hrefs, unless they're pointing outside the application. Links are dynamic elements, however, which means that they need one additional, nonstandard attribute: oid. "oid" stands for object identifier; it's the name associated with each dynamic element.
So in fact we want to write the following:
<a oid="log">Log</a> the current time.
What will happen when you click on that link? Well, by default, it will call a method on your component with the same name. So let's add a log method to the class:
def log @loggedTimes << Time.now end
We haven't ever initialized @loggedTimes, so we'll actually need to add one more method: the awake method. awake is called when a page is first created.
def awake @loggedTime = [] end
Ok! Our Main.html file is growing; it should now look something like this:
<% class Main < Iowa::Component def awake @loggedTimes = [] end def time Time.now end def log @loggedTimes << Time.now end end %> <html> <body> <h1>Hello world!</h1> <p> The current time is <b>@time</b>. <p> <a oid="log">Log</a> the current time. </body> </html>
Hit reload once more on your browser and make sure the updated page comes up.
The Log link is there, but if you click it right now, it doesn't do much. Well, maybe it does - it's supposed to be adding something to the @loggedTimes array - but since we're never displaying that array, it's hard to know.
If we're going to display @loggedTimes, the first thing we need to do is provide an accessor for it. You could write a method for it yourself, or you could let Ruby do it for you by adding this to the class definition:
attr_reader :loggedTimes
Now we can access that variable from the html. But how? Well, we could do the same thing we did for time - insert this into your template:
You have visited this page at @loggedTimes.
Hit reload. It'll look promising if you've only hit the Log link once, but otherwise you'll notice that it's not the most beautiful way of displaying the data. We need to get a little more sophisticated.
One way of displaying lists in html is with the <ul> (unordered list) tag. IOWA provides this as a dynamic element. We'll add this to the html:
<ul oid="logList"> <li>@logItem</li> </ul>
In the html, this list only has one item; because the <ul> tag is dynamic, however, it will repeat its contents as many times as necessary.
Of course, right now it has no way of knowing that it's supposed to display the @loggedTimes array. For that, we'll need to introduce a binding.
Bindings are the third part of an IOWA file; they sit in between the html and the Ruby, and specify any associations that aren't obvious. In particular, they associate properties of named dynamic elements either with literals (strings, numbers), or more often with properties of the component object. In this case, we want to add a binding for the "logList" element. We'll add the following to the file:
<? logList { list = loggedTimes item = logItem } ?>
The <ul> element, like other iterator dynamic elements (such as <table>), takes two properties: a list, and an item - an index variable. For each item in the list, it will set the index variable to that item, and then print its contents. (For a list of dynamic elements and their properties, see the API Reference.
We're telling the logList element to take @loggedTimes as its list, and to iterate over it with @logItem. Since inside the <ul> tag we print the value of @logItem, we should see a bulleted list with an entry for each logged time.
One more thing before we can test: logItem is a fictitious variable for now. We need to make it real - and it needs to be both readable and writable from the html. So add this to the class definition:
attr_accessor :logItem
The current file:
<% class Main < Iowa::Component attr_reader :loggedTimes attr_accessor :logItem def awake @loggedTimes = [] end def time Time.now end def log @loggedTimes << Time.now end end %> <? logList { list = loggedTimes item = logItem } ?> <html> <body> <h1>Hello world!</h1> <p> The current time is <b>@time</b>. <p> <a oid="log">Log</a> the current time. <p> <ul oid="logList"> <li>@logItem</li> </ul> </body> </html>
Reload and test.
It would be silly to go much further without introducing forms, the backbone of almost any web application. We'll extend our little application to allow visitors to attach text to each log entry.
Let's start with the html: instead of a link to log the current time, we'll use a text input box and a submit button. Perhaps something like this:
<form> Add a comment: <input type="text"><input type="submit" value="Add"> </form>
As you might have guessed, the reason these tags don't seem to have all the necessary attibutes is that we're going to make them dynamic. Or some of them, at least; any dynamic form elements must be enclosed in a dynamic form, and the text input is going to want to be dynamic as well. The submit button doesn't need to be unless it has a different action from the form - for example, when there are multiple submit buttons.
Fleshed out with oids, the html looks like this:
<form oid="log"> Add a comment: <input type="text" oid="comment"> <input type="submit" value="Add"> </form>
We're using the same "log" oid (and thus the log method) for the form that we previously used for the link, but the text input's oid is new. By default, a text input named "comment" will be bound to the "comment" attribute of the component; the input field will always display the current value of the attribute, and the attribute will always be updated to reflect submissions of the form.
So let's permit reading and writing on comment:
attr_accessor :comment
If we're going to use the comments, we'll need to update the log method to store them. If we were building a more complicated application, we'd probably build a custom class for the log items. For now, let's just use a hash.
def log @loggedTimes << {"time" => time, "comment" => @comment} end
Let's look at the source so far:
<% class Main < Iowa::Component attr_reader :loggedTimes attr_accessor :logItem attr_accessor :comment def awake @loggedTimes = [] end def time Time.now end def log @loggedTimes << {"time" => time, "comment" => @comment} end end %> <? logList { list = loggedTimes item = logItem } ?> <html> <body> <h1>Hello world!</h1> <p> The current time is <b>@time</b>. <p> <form oid="log"> Add a comment: <input type="text" oid="comment"> <input type="submit" value="Add"> </form> <p> <ul oid="logList"> <li>@logItem</li> </ul> </body> </html>
We've done enough at this point to test again. However, since we've changed the structure of our data, we should probably start over with a fresh instance of this component. In the URL field of your browser, delete the cryptic numbers and letters at the end, so that you're left with the original path you typed in; Iowa will start a new session for you, with a clean component.
Well, you'll notice the hash didn't print out very nicely. But that's easy to fix - in Iowa, hash and array items can be accessed through property paths just like any other attributes. So we'll change our html for printing the list to this:
<ul oid="logList"> <li>@logItem.time: @logItem.comment</li> </ul>
Add another comment; this time, the page should display properly.
(more to come...)
Not yet written.
Not yet written.
Definitely not yet written. But may include: