Example 6. Formatting and Navigation

Output from ERb

In even moderately-sized systems, we're often faced with this challenge: we want to change some of the HTML generated by the application — but, first, we need to figure out what view, partial, or helper generates that HTML.

As an example, imagine we want to change part of the HTML displayed below. This is real output from a pretty well-factored set of ERb views and partials, taken from a real, commercial application (albeit with the classes and model names changed).

  <div class="input boolean optional field_with_hint"><input name="user_preferences[visible_to_public]" type="hidden" value="0" /><label class="boolean optional control-label checkbox" for="user_preferences_visible_to_public"><input class="boolean optional" id="user_preferences_visible_to_public" name="user_preferences[visible_to_public]" type="checkbox" value="1" />Visible to Public</label><span class="hint">Enable visibility to public</span><div class="input_error"><span class="tooltip"></span></div></div>
  <div class="input boolean optional field_with_hint"><input name="user_preferences[email_over_sms]" type="hidden" value="0" /><label class="boolean optional control-label checkbox" for="user_preferences_email_over_sms"><input class="boolean optional" id="user_preferences_email_over_sms" name="user_preferences[email_over_sms]" type="checkbox" value="1" />Prefer email to SMS for communication</label><span class="hint">Prefer sending email to sending SMSes</span><div class="input_error"><span class="tooltip"></span></div></div>
  <div class="input string optional field_with_hint"><label class="string optional control-label" for="user_preferences_name_override">Show this name to public instead</label><input class="string optional" id="user_preferences_name_override" name="user_preferences[name_override]" size="50" style="width: 1070px" type="text" value="" /><span class="hint">Specify custom name to be shown instead of user's actual name</span><div class="input_error"><span class="tooltip"></span></div></div>

  <input name="commit" type="submit" value="Update User Preferences" />
</form>

<h4>Custom Blocking Rules</h4>
<table class="readable">
  <thead>
    <tr>
      <th>type</th>
      <th>pattern</th>
      <th>rules</th>
      <th>actions</th>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>
<a href="/admin/users/4832424/blocking_rules/new">create blocking patterns</a>

<h4>Friend Relationship Analysis</h4>

<a href="/admin/relationships/4832424">View Relationship Analysis</a><br />
<a href="/admin/relationships/4832424.csv">View Relationship Analysis as CSV</a>

<h4>Whitelisted Applications</h4>

Looking at that HTML:

  • Can you read it?
  • How would you tell which partial or view generates a given bit of HTML?

It ain’t pretty. This usually boils down to either guessing at views/partials until you stumble on the right one, or looking for uncommon strings in the code (CSS class names are usually a good one) and doing large-scale searches in your app/views tree to see where they show up.

Neither of these seem like things we should regularly be doing in 2015.

Output from Fortitude

If we translate the code into Fortitude, let's look at what we get instead. (We’ve made zero effort to “clean up” or otherwise refactor the Fortitude code.)

      <!-- BEGIN Views::Admin::Users::BooleanUserPreference depth 2: :name => "visible_to_public", :display => "Visible to Public", :hint => "Enable visiblity to public" -->
      <div class="input boolean optional field_with_hint">
        <input name="user_preferences[visible_to_public]" type="hidden" value="0">
        <label class="boolean optional control-label checkbox" for="user_preferences_visible_to_public">
          <input class="boolean optional" id="user_preferences_visible_to_public" name="user_preferences_visible_to_public" type="checkbox" value="1">
          Visible to Public
        </label>
        <span class="hint">Enable visiblity to public</span>
        <div class="input_error">
          <span class="tooltip"></span>
        </div>
      </div>
      <!-- END Views::Admin::Users::BooleanUserPreference depth 2 -->
      <!-- BEGIN Views::Admin::Users::BooleanUserPreference depth 2: :name => "email_over_sms", :display => "Prefer email to SMS for communication", :hint => "Prefer sending email to sending SMSes" -->
      <div class="input boolean optional field_with_hint">
        <input name="user_preferences[email_over_sms]" type="hidden" value="0">
        <label class="boolean optional control-label checkbox" for="user_preferences_email_over_sms">
          <input class="boolean optional" id="user_preferences_email_over_sms" name="user_preferences_email_over_sms" type="checkbox" value="1">
          Prefer email to SMS for communication
        </label>
        <span class="hint">Prefer sending email to sending SMSes</span>
        <div class="input_error">
          <span class="tooltip"></span>
        </div>
      </div>
      <!-- END Views::Admin::Users::BooleanUserPreference depth 2 -->
      <!-- BEGIN Views::Admin::Users::StringUserPreference depth 2: :name => "name_override", :display => "Show this name to public instead", :hint => "Specify custom name to be shown instead of user's actual name" -->
      <div class="input string optional field_with_hint">
        <label class="string optional control-label" for="user_preferences_name_override">
          Show this name to public instead
        </label>
        <input class="string optional" id="user_preferences_name_override" name="user_preferences_name_override" size="50" style="width: 1070px" type="text" value="">
        <span class="hint">Specify custom name to be shown instead of user&#39;s actual name</span>
        <div class="input_error">
          <span class="tooltip"></span>
        </div>
      </div>
      <!-- END Views::Admin::Users::StringUserPreference depth 2 -->
      <!-- END Views::Admin::Users::PreferencesList depth 1 -->
      <!-- BEGIN Views::Admin::Users::CustomBlockingRules depth 1 -->
      <h4>Custom Blocking Rules</h4>
      <table class="readable">
        <thead>
          <tr>
            <th>type</th>
            <th>pattern</th>
            <th>rules</th>
            <th>actions</th>
          </tr>
        </thead>
        <tbody></tbody>
      </table>
      <a href="/admin/users/4832424/blocking_rules/new">create blocking patterns</a>
      <!-- END Views::Admin::Users::CustomBlockingRules depth 1 -->
      <!-- BEGIN Views::Admin::Users::RelationshipAnalysis depth 1 -->
      <h4>Friend Relationship Analysis</h4>
      <a href="/admin/relationships/4832424">View Relationship Analysis</a>
      <br>
      <a href="/admin/relationships/4832424.csv">View Relationship Analysis as CSV</a>
      <!-- END Views::Admin::Users::RelationshipAnalysis depth 1 -->
      <!-- BEGIN Views::Admin::Users::WhitelistedApplications depth 1 -->
      <h4>Whitelisted Applications</h4>

What’s happened here?

  • Formatting: All of our HTML is perfectly indented according to the actual structure of the DOM…and, unlike many templating engines, this even works perfectly across partials. (Your entire page will be indented properly, all the way down, no matter what the structure of your views or partials is.) This alone makes the output vastly more readable.
  • Comments: Fortitude automatically adds comments to the generated HTML output, every time a widget is invoked. These comments tell you exactly what widget class is being rendered, what variables it was passed, and how deeply down the stack of views/partials it’s been nested (which can be completely different from the nesting/indentation depth of the DOM/HTML structure itself).

    At this point, it’s trivial to see exactly what view or partial contains the source code for any given section of HTML output.

Just through these two small features alone, Fortitude makes it a great deal easier to figure out what’s going on in your rendered HTML and trace it back to the source code.

But What About Production?

In production, by default, both these features are switched off. The lack of comments means you won’t expose the internal structure of your source code, and the lack of formatting means that you’ll actually get better HTML output in production with Fortitude than with ERb. In production, Fortitude emits tags with no whitespace between them, causing the server to transmit as few bytes as possible to the client for a given section of rendered HTML.

In our next example, we’ll look at the last of Fortitude’s major features: its runtime performance. Given that Fortitude is quite a lot more sophisticated than other templating engines, how much does it pay for that advantage in maintainability and factorability?