In Progress
Unit 1, Lesson 1
In Progress

Roda – Part 2

In a previous episode, guest chef Federico Iachetti showed us how to get up and running with the lightweight Roda web toolkit. Today, he’s going to demonstrate how to work with HTML views in this deceptively powerful little library. Enjoy!


“For a comprehensive guide to developing web applications with Roda, check out Federico’s Roda Course!

Video transcript & code

In the previous episode, we wrote a Roda application that captures emails from the user. We ended up with this code.


  EMAILS = [
    'fede@example.com',
    'avdi@example.com'
  ]

  class App < Roda

    route do |r|
      r.on "emails" do
        r.get do
          r.root do
            [
              "<ul>",
              EMAILS.map {|email| "  <li>#{email}</li>"},
              "</ul>",
            ].join("\n")
          end

          r.is "form" do
            <<~EOF
              <form action="/emails" method="post">
                <label for="email">Email</label>
                <input name="email" type="text" value=""/>
              </form>
            EOF
          end
        end

        r.post do
          EMAILS << r.params["email"]
          r.redirect "/emails/"
        end
      end

    end
  end

Which works fine. But we barely scratched the surface of what Roda can provide.

So far, we've been creating HTML strings by hand. But this is far from ideal.

In order to help us avoid this (and many other) annoyances, Roda ships with an extensive plugins library. Including plugins for working with HTML views!

Lets get rid of the ugly inline HTML strings. To do so, we add the render plugin to the app. The default templating language it uses is ERB, but it supports many different template engines through the Tilt gem. Say we want to use the slim syntax.


class App < Roda
  plugin :render, engine: "slim"

  # ...
end

Now, we can replace the inline HTML with a call to the view method. Lets give this view the emails/list name, and pass the emails as local variables (just the way we do with Rails).


# ...

r.root do
  view "emails/list", locals: {emails: EMAILS}
end

# ...

When we try to load the page, we see that there's an error: the template file is missing. And the error shows us the file we need to create.


GET "/emails/"
# => "STATUS: 500 Internal Server Error"

error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/fedex/Dropbox/rubytapas-guest/apricot_cocktail_ii_with_federico_iachetti/examples/episode2/email_capture1/views/emails/list.slim"

Lets provide it

views/emails/list.slim


ul
  - emails.each do |email|
    li= email

Now when we reload, we get a second error.

This is telling us Roda looked for a layout file, but couldn't find it.


GET "/emails/"
# => "STATUS: 500 Internal Server Error"

error
# => "Errno::ENOENT: No such file or directory @ rb_sysopen - /home/fedex/Dropbox/rubytapas-guest/apricot_cocktail_ii_with_federico_iachetti/examples/episode2/email_capture1/views/layout.slim"

We create it, and we add yield where we want our page-specific content to be added.

views/layout.slim


html
  head

  body
    .pageWrapper
      .content
        == yield

We also change our /emails/form route to use a view.

And put our template in place.

And now when we reload again, they work.

Since we have 2 pages we can access in our app, some navigation would be nice.

We can add it to the layout template but, for organization purposes we want to create a partial for it.

To do this, we use the partials plugin.


class App < Roda
  plugin :render, engine: "slim"
  plugin :partials

  # ...
end

And, with that in place, we can use the partial method to add our partial to the layout passing the partial name as an argument.

We'll pass the current path as a local variable in order to \"highlight\" the current page.

views/layout.slim


html
  head

  body
    == partial "nav", locals: { current_path: request.path }

    .pageWrapper
      .content 
        == yield

And now we create the template for the partial. The convention for partials is the same Rails uses: adding a leading underscore to the partial file name.

In the template we add a nav element with a nested unordered list. Then we add a list item for each path.

If the current element on the iteration matches the current path, we add the .currentPath class. Then a link to our current path. We also add current in parens for now.

views/~nav~.slim


nav.navbar
  ul
    - %w[/emails/ /emails/form].each do |path|
      li[class=(:currentPath if path == current_path)]
        a[href=path]
          = path
          = " (current)" if path == current_path

And now we can see that everything works. We can navigate to both our views using the navigation bar.

But it looks pretty ugly. Lets add some style to the mix. For this we can use the assets plugin to include a stylesheet.

We'll give it a list of stylesheet files to be included under the key :css. We could also add JavaScript files this way, if we wanted to.

We also need to add the r.assets route to make these asset files available to clients.


class App < Roda
  plugin :render, engine: "slim"
  plugin :partials
  plugin :assets, css: %w[main.scss]

  route do |r|
    r.assets

    # ...
  end
end

Then, we add the assets method in our layout, specifying the asset type :css in order for the plugin to add the appropriate link tags.


html
  head
    == assets(:css)

  body
    == partial "nav", locals: { current_path: request.path }

    .pageWrapper
      .content
        == yield

And now we can add our style and script files to the assets/css directory.


$text-color: #282828;
$background-color: #eeeeee;

body {
  color: $text-color;
  background-color: $background-color;
  margin: 0px;

  font-size: large;
}

a {
  color: $text-color;
  text-decoration: none;

  &:visited, &:hover {
    color: $text-color;
    text-decoration: none;
  }

  &:hover {
    font-weight: bold;
  }
}

.pageWrapper {
  display: flex;
  justify-content: center;
}

.content {
  width: 80%;
  padding: 50px;
  background-color: lighten($background-color, 40%);

  border-radius: 10px;
  /* offset-x | offset-y | blur-radius | color */
  box-shadow: 3px 3px 5px 1px rgba(0, 0, 0, 0.3);

}

nav.navbar {
  top: 0;
  left: 0;

  width: 100%;
  height: 50px;

  margin-bottom: 25px;

  ul {
    & * {
      color: white;
    }

    $nav-color: #787878;
    display: flex;

    height: 100%;
    padding: 0;

    background-color: $nav-color;

    justify-content: space-around;

    li {
      display: flex;
      justify-content: center;
      align-items: center;

      list-style: none;
      width: 100%;

      &.currentPath {
        border:5px solid $nav-color;
        background-color: darken($nav-color, 8%);
        font-weight: bold;
      }

      a {
        width: 100%;
        height: 100%;

        display: flex;
        justify-content: center;
        align-items: center;
      }
    }
  }
}

Now, when we go to http://localhost:9292/emails we see our new style applied to both pages of our site.

Roda ships with a lot more plugins worth taking a look at that will make your life easier. To learn more, you can visit Roda's site.

Happy hacking!

Responses