Shoes Meets Merb: Driving a GUI App through Web Services in Ruby
Pages: 1, 2, 3, 4, 5, 6
Building a Shoes Frontend
Now we can get down to business and build the user interface for our pastebin. Documentation for Shoes is scant, but the best reference is the book Nobody Knows Shoes, by why the lucky stiff himself. The official API reference is available online as The Entirety of the Shoes Family.
The Shoes portion of the code is fairly simple, and should be easy to understand if you have experience programming for the Web, even if you have not used Shoes before. We will present it in its entirety, and then dissect and explain it. Because Shoes evals the code it is provided with, all of the code for a Shoes application should be in one file. Therefore, this code should follow the HttpToYaml code that we showed earlier:
class ShoesPaste < Shoes
include HttpToYaml
url '/', :index
url '/new', :new_paste
url '/(\d+)', :show_paste
def index
pastes = get "/pastes"
stack :margin => 20 do
title "Shoes Pastebin"
pastes.sort_by{|p| p[:created_at]}.each do |paste|
para link(paste[:title], :click => "/#{paste[:id]}")
end
para link('New paste', :click => "/new")
end
end
def show_paste(id)
paste = get "/pastes/#{id}"
stack :margin => 20 do
title paste[:title]
para paste[:text], :font => 'Monospace 12px'
button "delete" do
if confirm("Are you sure you want to delete this paste?")
delete "/pastes/#{paste[:id]}"
visit "/"
end
end
view_all_link
end
end
def new_paste
stack :margin => 20 do
title "New paste"
flow(:margin_top => 20) { caption "Title: "; @title = edit_line }
@text = edit_box :margin_top => 20, :width => 400, :height => 200
button("Paste!", :margin => 5) do
paste = post '/pastes', :title => @title.text, :text => @text.text
visit "/#{paste[:id]}"
end
view_all_link
end
end
protected
def view_all_link
para(link('View all pastes', :click => '/'), :margin_top => 20)
end
end
Shoes.app :title => 'Shoes Pastebin', :width => 640, :height => 400
We inherit from Shoes to provide a clean environment in which to contain our application code. It is possible to contain the entire application in a block passed to the Shoes.app method, but it gets messy with larger applications. Our method allows us to keep our application code contained in one class.
After the initial include statement that pulls in our web service bridge, we set up the URLs for our application using the Shoes.url class method. This is a potential source of confusion because these URLs are completely different from those exposed by our Merb application. Because Shoes is modeled after the Web, it uses pseudo-URLs to reference different sections within an application.
url '/', :index
url '/new', :new_paste
url '/(\d+)', :show_paste
Each url statement maps a regular expression to a method that is executed when a URL matching that regexp is "visited." (URLs can be triggered with a :click action on some elements, or with the visit method, as we will see later.) Any captures in the regular expression (such as (\d+)) will be passed as arguments to the method named.
We have three types of pages: the index, which shows a list of pastes, the "new paste" screen, and the screen to show an existing paste. Let's look at the index first, as it will introduce us to several aspects of the Shoes API.
def index
pastes = get "/pastes"
stack :margin => 20 do
title "Shoes Pastebin"
pastes.sort_by{|p| p[:created_at]}.each do |paste|
para link(paste[:title], :click => "/#{paste[:id]}")
end
para link('New paste', :click => "/new")
end
end
The first line of the index method retrieves all of the current pastes from the /pastes URL on the server. As we saw before when experimenting with irb, this will be an array of hashes, with each hash containing attributes of one paste.
The stack method is unique to Shoes. For layout, Shoes uses the concept of stacks and flows, which are two types of boxes. Either type of box contains a series of elements. Stacks order their contents from top to bottom, while flows organize them from left to right (wrapping lines if necessary). We will use both stacks and flows to lay out different parts of our application. This method call also illustrates that most methods accept a hash of styles to fine-tune their positioning and look.
Within the stack, we first have a title, which is a simple text heading. Rather than HTML's unimaginative h1 through h6, Shoes uses creative heading names: banner, title, subtitle, tagline, caption, and inscription.
Next in the stack, we sort the pastes by their creation time, and create a paragraph (para) containing a link to that paste's show_paste method. (The "/#{paste[:id]}" URL will match the '/(\d+)' regexp.) Finally, we close out the stack by linking to the "new paste" URL.
When we run this with shoes /path/to/shoes_paste.rb, we see the following:

Now we probably want to be able to see some of the pasted code. So we will look at the show_paste function that lets us get at the pastes:
def show_paste(id)
paste = get "/pastes/#{id}"
stack :margin => 20 do
title paste[:title]
para paste[:text], :font => 'Monospace 12px'
button "delete" do
if confirm("Are you sure you want to delete this paste?")
delete "/pastes/#{paste[:id]}"
visit "/"
end
end
view_all_link
end
end
protected
def view_all_link
para(link('View all pastes', :click => '/'), :margin_top => 20)
end
Because show_paste's mountpoint (its URL regular expression) has one capture, this function takes one argument: the ID of the paste. A URL of /123 will translate into a call to show_paste(123). Once we have that ID, we interpolate it into an actual URL on the Merb server, and retrieve the paste. The web service bridge does the translation from YAML to a hash, so we can work directly with the attributes of the paste.



