In our last example, we saw how using inheritance lets you easily solve view-factoring problems with Fortitude that remain painful in traditional templating engines. In this example, we’ll see how Fortitude’s widget classes let us create specialized contexts for rendering parts of views. In effect, they become little mini-languages, introducing view primitives exactly when you need them and providing an elegant language for you to describe what your view should look like.
Almost everybody knows the concept of a modal dialog — a “layer” of HTML that requests some kind of input or confirmation from the user before proceeding, and which obscures the rest of the web page so that the user is forced to provide this input or confirmation before proceeding. They’re often also known as a lightbox, modal window, or heavy window, and have become a staple of modern Web design.
Here’s an example of a modal dialog that appears in Google Docs when you choose “Make a Copy”:
This simple example has a number of features that modal dialogs tend to share:
These simple dialogs present an interesting challenge for view builders. Even the frame of the dialog will consist of multiple elements (usually divs), with some important CSS classes applied. There will be a standard way of creating the title, and likely a container that the primary content needs to go into. The buttons at the bottom will typically have their own container and styles, and standard ways of rendering them.
Let’s look at an example modal dialog in completely non-refactored (one-off) HTML:
<div class="modal-background copy_document_modal">
<div class="modal-dialog">
<div class="window-controls"><span class="dialog-control">X</span></div>
<div class="modal-title">
<h3>Copy document</h3>
</div>
<div class="modal-contents">
<form class="modal-form">
<label for="document_name">Enter a new document name:</label>
<input type="text" name="document_name">Copy of Untitled document</input>
<span class="form-input-footnote">Comments will not be copied to the new document.</span>
<input type="checkbox" name="copy_sharing"></input>
<label for="copy_sharing">Share it with the same people</label>
</form>
</div>
<div class="modal-actions">
<button name="OK" value="ok" class="modal-button">OK</button>
<button name="Cancel" value="cancel" class="modal-button">Cancel</button>
</div>
</div>
</div>
Using traditional templating engines, what’s the best we can do with this? In many ways, it’s similar to our previous example: we can either use partials/helpers for shared structural elements and count on calling them in the right order, or use capture to construct blocks of HTML and pass them into a shared view.
One of the big differences here is that the chunks of HTML we pass in themselves need certain predefined structure. For example, the modal title may need to be in a certain format (at least most of the time), and the set of buttons at the bottom may need certain classes, and so on.
Without further ado, let’s see what the end result looks like:
<% the_modal_title = capture do %>
<%= modal_title("Copy document") %>
<% end %>
<% the_modal_contents = capture do %>
<%= modal_form_start %>
<label for="document_name">Enter a new document name:</label>
<input type="text" name="document_name">Copy of Untitled document</input>
<span class="form-input-footnote">Comments will not be copied to the new document.</span>
<input type="checkbox" name="copy_sharing"></input>
<label for="copy_sharing">Share it with the same people</label>
<%= modal_form_end %>
<% end %>
<%= render :partial => '/shared/modal_dialog', :locals => {
:outer_css_class => 'copy_document_modal',
:title_html => the_modal_title,
:contents_html => the_modal_contents,
:buttons => { 'OK' => 'ok', 'Cancel' => 'cancel' }
}
%>
And our shared modal dialog partial:
<div class="modal-background <%= outer_css_class %>">
<div class="modal-dialog">
<% if defined?(include_close) && include_close %>
<div class="window-controls"><span class="dialog-control">X</span></div>
<% end %>
<div class="modal-title">
<%= title_html.html_safe %>
</div>
<div class="modal-contents">
<%= contents_html.html_safe %>
</div>
<div class="modal-actions">
<% buttons.each do |title, value| %>
<button name="<%= title %>" value="<%= value %>" class="modal-button"><%= title %></button>
<% end %>
</div>
</div>
</div>
And, finally, our helpers:
# ...
def modal_title(text)
"<h3>#{text}</h3>"
end
def modal_form_start
"<form class=\"modal_form\">"
end
def modal_form_end
"</form>"
end
# ...
Have we achieved what we want with a traditional templating engine? Yes. Is it pretty? No.
In particular, we can see quite a few places where this code is pretty messy:
capture, then pass them into the partial it invokes. The source code doesn’t reflect the structure of the resulting output.
_modal_dialog.html.erb, they’ll remember to remove those helper methods?)
application_helper.rb file.
OK, OK. How much better can Fortitude make this? Here’s our caller, written in Fortitude:
class Views::Docs::CopyDocument < Views::Base
def content
widget Views::Shared::ModalDialog.new(:outer_css_class => 'copy_document_modal') do
title 'Copy document'
form {
label "Enter a new document name:", :for => :document_name
input "Copy of Untitled document", :type => :text, :name => :document_name
footnote "Comments will not be copied to the new document."
input :type => :checkbox, :name => :copy_sharing
label "Share it with the same people", :for => :copy_sharing
}
buttons {
button 'OK'
button 'Cancel'
}
end
end
end
And here’s our shared code:
class Views::Shared::ModalDialog < Views::Base
needs :outer_css_class, :include_close => true
def content
wrapper {
controls
yield
}
end
def wrapper
div(:class => [ 'modal-background', outer_css_class ]) {
div(:class => 'modal-dialog') {
yield
}
}
end
def controls
div(:class => 'window-controls') {
span "X", :class => 'dialog-control'
}
end
def title(s)
h3 s
end
def form
div(:class => 'modal-contents') {
form(:class => 'modal-form') {
yield
}
}
end
def footnote(s)
span s, :class => 'form-input-footnote'
end
def buttons
div(:class => 'modal-actions') {
yield
}
end
def button(title, value = nil)
value ||= title.downcase
button(title, :name => title, :value => value, :class => 'modal-button')
end
end
What have we done here?
In effect, we’ve used Fortitude to introduce a miniature DSL, designed specifically for creating modal dialogs. In this language, certain terms get redefined — title and form conceptually mean the same thing, but cause different output than they would in ordinary HTML — while we’ve also added new concepts like footnote.
Fortitude’s widget system lets us scope this DSL precisely: within the context of the ModalDialog widget and blocks passed to it, we redefine title and form, and add footnote, but, elsewhere, they revert back to their standard meanings (or no meaning at all, in the case of footnote).
And, as you might imagine, the sky is the limit: because Fortitude is built with all the same tools as standard Ruby, you have full control over exactly how this works, and can implement in any way you can imagine:
sidebar primitive that works across your whole application? You can. Just add it to your base widget class, and you’re all set.
user_history method that works only in your admin views? Easy — if you have a base widget class all your admin views inherit from, add it there, or else put it in a module and mix it in to any admin view that needs it.
strong for bold text and em for italics. Wish it had a highlighted primitive to give you text on a colored background? With about three lines of Fortitude code and three lines of CSS, it does now.
b text than the now-correct strong? Again, about three lines of Fortitude code will mean you can write b, but get HTML5-correct strong instead.
a method to inspect the passed parameters, and emit a warning or error in development mode if it’s non-SSL. This is also about five lines of code.
row { ... } than <div class='row'>...</div>. Again, this is a very small amount of code in Fortitude.
Hopefully this gives you a taste of all of the different things you can easily do with Fortitude. Using Fortitude, you can sculpt your view language to your own needs, both on a site-wide basis and in specific contexts. It’s an immensely powerful tool for building far cleaner views — not only making them more readable and maintainable, but also a whole lot more fun.
Next, we’ll take a look at some of the other benefits Fortitude offers beyond the abilities it gives you to factor your code.