One awesome thing about the iPhone is that it can display documents very nicely, including Word, Excel, and PDF files. However, the other day I was complaining that it's not very easy to view the documents you store on your computer on your iPhone. Sure, you could email them to yourself, but then you have to search through your mail on your iPhone to find your documents. And I'm sure there is a snazzy iPhone app from the App Store to do this as well. But instead, let's build a quick web app using Sinatra and iUI.
Here's what we'll be building (screenshot courtesy of iPhoney, which rules, by the way):
Sinatra is a really awesome minimalist web framework. It lets you build web applications with just a few lines of code. iUI is a collection of JavaScript, CSS, and images that lets you easily make your web sites look great on the iPhone. Using these two tools, it's really easy to build simple iPhone apps.
To begin, install the Sinatra gem:
> gem install sinatra
Now, let's start with a simplest version of our app, which we'll call 'butler'. Let's make a directory for butler.
> mkdir butler
> cd butler
> touch butler.rb
Open up butler.rb in your favorite editor and type:
Now start butler on your command line: > ruby -rubygems ./butler.rb and point your browser to http://localhost:4567 (you can use your computer's browser or the one on your iPhone - it doesn't matter. I find it's better to use the one on my computer while building the app, since it's easier to read Sinatra's debugging messages if something goes wrong). You should see a page that just says "Your files, sir." Congrats! You've made your first Sinatra app. Wasn't that easy?
OK, let's make butler a little more useful. Sinatra will serve up any files in a subdirectory named public. Since we'll eventually be using this public directory for holding other JavaScript and CSS files as well, we'll actually put our files in ./public/files. We'll also make a link for convenience. Finally, while we're at it, let's put a few test files in there.
> mkdir -p public/files
> ln -s public/files files
> echo "foo" > public/files/foo.txt
> echo "bar" > public/files/bar.txt
We want butler to link to each file, so let's build a little helper for that. In Sinatra, you can include helpers within a helper block. We'll also try out our helper for one file.
Go refresh your browser to see the changes. There's no need to restart your application, because Sinatra automatically reloads changes (very cool!). You should see a link to foo.txt. Click on it, and you'll see the contents.
Clearly, we don't want to hardcode this for just one file. Let's alter butler to look for every file within the ./files directory.
OK, refresh your browser and you should see both foo.txt and bar.txt. This is looking pretty good, but we're not really creating valid HTML right now. We're missing html, head, and, body tags at the very least. We could add this all within our "get" handler, but that would clutter up the code.
Instead, let's put this code into a view. Sinatra actually lets you put the view right after your other code, so you can build an entire application in one file. For simplicity, I'm going to do that for this tutorial. However, if this approach bothers you (or just messes up syntax highlighting in your editor), rest assured you can place the view code in a views directory and it would work the same way.
Let's add the view to the end of our file, and use it in our handler. Notice that I name the view 'index' by beginning my declaration with @@ index - if I wanted a separate file, I would just put it in ./views/index.erb (you can also use Haml, if that's your cup of tea). Note I assign @links in the handler and it automatically is available in the view.
Refreshing the browser now isn't really that exciting, since things look the same, but if you wanted, you could easily play around with the view to make things look different.
One glaring problem is that this page isn't very usable on the iPhone itself. That's where iUI comes in. Start by downloading it (URL is in instructions below) to your butler directory, unzipping it, and copying the necessary files into your public directory.
> mkdir iui
> cd iui
> wget http://iui.googlecode.com/files/iui-0.13.tar.gz
> tar -xzvf iui-0.13.tar.gz
> cd ..
> mkdir public/images
> cp iui/iui/*.png public/images
> cp iui/iui/*.gif public/images
> mkdir public/javascripts
> cp iui/iui/*.js public/javascripts
> mkdir public/stylesheets
> cp iui/iui/*.css public/stylesheets
To use iUI, you'll need to include the JavaScript and CSS in your view. You'll also need to add some elements to the body of your view. When you're done, the view will look like this:
This html is probably a bit confusing, but don't worry. There are a few examples in ./iui/samples/ to learn from (and good iUI tutorials on the web). Finally, you'll want to alter the file_link helper to print out iUI code, like so:
Note that target='_self' code. You need that to get iUI to open a link in a normal way. If you leave it off, it will use an AJAX call to load the file within the current page, which looks really funny when you try to open a binary file like a PDF.
The final code looks like this:
And there you have it - an iPhone web app in less than 50 lines of code, thanks to Sinatra and iUI. Now, whenever you want to view some files on your iPhone, either copy the file:
> cp path/to/my_file ./files
or if you prefer, link it:
ln -s path/to/my_file ./files
... and then run butler
ruby -rubygems ./butler.rb
Figure out the IP address of your computer and simply point your iPhone browser to http://<ip>:4567
I use butler primarily within my home network, but if you want to be able to view your files on the go, you'll need to poke a hole in your firewall. That's a bit outside the scope of this tutorial, but a quick Google search should give you some good results.
Enjoy!
Update: Removed an unused parameter from the code after pmccann called it to my attention.
Update: Added -z option to tar after Peter pointed out the omission. The tar command without -z worked for me on OS X 10.5, but this is definitely more correct.
Update: Added -rubygems option to ruby command. If you'd prefer to not use this option, check the comments below for ways to use RubyGems in a Ruby script.


