Search This Blog

Exploring Monster Taming Mechanics in Final Fantasy XIII-2: Viewing More Data

In the previous episode of this Final Fantasy XIII-2 monster taming mechanics series, we started looking at the view and controller parts of the Rails MVC architecture by building views of the monster material table and the site index. Now it's time to expand our views to some more of the simpler tables, and in so doing, we'll need to improve our site navigation so that we don't have to keep going back to the home page to get anywhere else. We'll build up the monster characteristics and game locations views so that we have something to fill out our navigation, and then we can see how easy it is to build a site-wide navigation bar using Bootstrap. Let's get started.

Final Fantasy XIII-2 battle scene with Cactuar and Chocobo

Create a Monster Characteristics Page

Creating the monster characteristics page is as simple as it was to create the monster materials page last time. First, we run the rails controller generator for it:
$ rails g controller Characteristic index
Remember, this creates Characteristic controller and view files and adds the route to the view page to the top of the config/routes.rb file:
Rails.application.routes.draw do
  get 'characteristic/index'
  get 'material/index'
  get 'home/index'
  root 'home#index'
end
With this addition to the routes, we can load the page at http://localhost:3000/characteristic/index after starting the Rails server, but first we want to load the table data in the controller and render it in a table in the view. First, the controller in app/controllers/characteristic_controller.rb:
class CharacteristicController < ApplicationController
  def index
    @characteristics = Characteristic.all
  end
end
With all the characteristics loaded into the @characteristics instance variable, we can make a nice HTML table by duplicating the material view table we created last time in app/views/characteristic/index.html.erb and making the necessary modifications:
<h1>Monster Characteristics</h1>

<table id="characteristic-table" class="table table-striped table-sm">
  <thead class="thead-dark">
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Description</th>
    </tr>
  </thead>

  <% @characteristics.each do |characteristic| %>
    <tr>
      <td><%= characteristic.name %></td>
      <td><%= characteristic.description %></td>
    </tr>
  <% end %>
</table>
Finally, we add the same formatting to the CSS file at app/assets/stylesheets/characteristic.scss to tighten up the table a bit:
#characteristic-table {
  width: 700px;
}
We've quickly built another pretty table:

Monster Characteristics table

We should also add this page as another link to our index in app/views/home/index.html.erb so we can more easily get to it:
<h1>Final Fantasy XIII-2 Monster Taming</h1>
<%= link_to 'Monster Materials', material_index_path %>
<%= link_to 'Monster Characteristics', characteristic_index_path %>

Create a Game Location Page

Just to weigh down our site navigation a bit more and show that we really need something better than the back button, let's add in one more table for the game locations. First, we'll run the generator again, and then I'll quickly list off the file changes since this should all be routine now:
$ rails g controller Location index
This command creates a Location controller and view, and adds a route to the top of the config/routes.rb file:
Rails.application.routes.draw do
  get 'location/index'
  get 'characteristic/index'
  get 'material/index'
  get 'home/index'
  root 'home#index'
end
Next, load the location data in app/controllers/location_controller.rb:
class LocationController < ApplicationController
  def index
    @locations = Location.all
  end
end
Then, make the HTML table in app/views/location/index.html.erb:
<h1>Game Locations</h1>

<table id="location-table" class="table table-striped table-sm">
  <thead class="thead-dark">
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Source Area</th>
    </tr>
  </thead>

  <% @locations.each do |location| %>
    <tr>
      <td><%= location.name %></td>
      <td><%= location.source&.name %></td>
    </tr>
  <% end %>
</table>
Notice the special '&.' operator in location.source&.name? This operator was used because location.source is another location object so we need to get that location's name, but in the case of New Bodhum 003 AF, there is no source location. The '&.' operator will stop and return this nil value instead of trying to call .name on nil, which would be bad. Thus, we have a nice clean syntax instead of needing to write location.source && location.source.name. Moving on, we finally add the table formatting to app/assets/stylesheets/location.scss:
#location-table {
  width: 500px;
}
We've quickly built yet another pretty table:

Game Locations table

Eventually, we're going to want to link this table and the monster characteristics table back and forth with the main monster table data so that we can list all of the monsters in a selected location or with a selected characteristic or find all the locations where a selected monster can be found, but for now these are just stand-alone tables.

Rethinking Navigation

After adding the game location page to the index at app/views/home/index.html.erb:
<h1>Final Fantasy XIII-2 Monster Taming</h1>
<%= link_to 'Monster Materials', material_index_path %>
<%= link_to 'Monster Characteristics', characteristic_index_path %>
<%= link_to 'Game Locations', location_index_path %>
I take a look at my index page and realize it's very plain:

Plain index screenshot

We can certainly do better, both visually and functionally. Visually, we'll space things out more to make it more readable. Functionally, we want to be able to hit those links more easily so we're going to make them bigger and pull them off the edge of the page. Beyond the index page, our navigation is non-existent. Every time we select a table to look at, the only ways to get to another table are to click the back button in the browser or (shudder) type the correct address into the address bar. We're going to fix that by adding a global navigation bar to all of the pages.

First, let's tackle that index page. Looking over Bootstrap's page component options, I kind of like the list group links for the list of tables and the display heading for a nice, big title for the page. We can change the index to use these Bootstrap components easily enough by adding the appropriate HTML elements and classes:
<h1 class="display-3 text-center">Final Fantasy XIII-2</h1>
<h1 class="display-3 text-center">Monster Taming</h1>
<div class="container">
  <div class="row">
    <div class="col-sm"></div>
    <div class="col-sm">
      <ul class="list-group">
        <%= link_to 'Monster Materials', material_index_path,
          class: "list-group-item list-group-item-action" %>
        <%= link_to 'Monster Characteristics', characteristic_index_path,
          class: "list-group-item list-group-item-action" %>
        <%= link_to 'Game Locations', location_index_path,
          class: "list-group-item list-group-item-action" %>
      </ul>
    </div>
    <div class="col-sm"></div>
  </div>
</div>
This may look like a lot more structure, but it's there to make use of Bootstrap's grid layout. The extra <div> elements set up a grid with three columns for the list of links, and the links are put in the center column so that the border around the links themselves doesn't stretch all the way across the page. The necessary classes for the links are added to the link_to methods with the class option, making it easy to add the desired formatting to links. Now our index page has some visual punch and is easier to use as well:

Fancy index screenshot

Okay, I'm sure there's plenty more we could do with this to make it more pleasing to the eye, but I'm an engineer, not a designer. We can move on to the global navigation. For this component, we're going to want to use a fixed navbar with a title element and a set of links for the different table pages. We'll start with a basic navbar for now, and we may need to extend it with some drop-down menus or a search form after we add more features to the site. Since we want this navbar to appear on every page, but we don't want to repeat the HTML everywhere, we should put it somewhere that will be rendered for every page auto-magically. Luckily, Rails has just such a place in app/views/layouts/application.html.erb:
  <body>
    <%= render 'layouts/header' %>
    <main class="container">
      <%= yield %>
    </main>
  </body>
The HTML inside the <body> tag used to just be the <%= yield %> statement that runs the appropriate controller for the page that's loading and renders the view for that page from the appropriate view file. We don't want to just drop all of the navbar code in this file, so we add a helper file and tell the renderer to fetch it before rendering the rest of the page. We also wrap a <main> tag with a Bootstrap class of container around the rest of the page so that we can more easily control the global formatting of the pages. Adding this container class adds some better page margin spacing by default.

Getting back to that header file, it actually resides at app/views/layouts/_header.html.erb because all helper views get an underscore prepended to them to differentiate them from regular views. That header with our navbar looks like this:
<nav class="navbar fixed-top navbar-expand navbar-dark bg-dark">
  <%= link_to "FFXIII-2 Monster Taming", root_path, 
    class: "navbar-brand" %>
  <ul class="navbar-nav">
    <li class="nav-item">
      <%= link_to "Home", root_path, class: "nav-link" %>
    </li>
    <li class="nav-item">
      <%= link_to "Monster Materials", material_index_path, 
        class: "nav-link" %>
    </li>
    <li class="nav-item">
      <%= link_to "Monster Characteristics", characteristic_index_path, 
        class: "nav-link" %>
    </li>
    <li class="nav-item">
      <%= link_to "Game Locations", location_index_path, 
        class: "nav-link" %>
    </li>
  </ul>
</nav>
Okay, what does all of this stuff do? Starting with the <nav> tag, the classes work as follows:

  • navbar: applies general navigation bar formatting
  • fixed-top: freezes the navbar at the top of the browser window
  • navbar-expand: puts the navbar elements in a horizontal line
  • navbar-dark: makes the navbar dark themed
  • bg-dark: makes the background dark and goes with the dark theme, otherwise the navbar would be invisible
With those general navbar options set up, the next tag is a link with the class navbar-brand. This link sets up a page logo that you can click on to go to the home page, and it's set off from the other links. Next, we have an unordered list with the class navbar-nav, which applies formatting for this to be a list of navigation links. Each list item gets a class of nav-item and each link gets a class of nav-link so the proper formatting can be applied to those elements, too. Now we can take a look at our nice new navigation bar:

Screenshot with overlapping navbar

Oops, that doesn't look quite right. The navbar is covering up some of our page because it's a fixed element at the top of the page. We need to drop the rest of the page down a bit, and the easiest way to do that is to add some padding to the <body> tag in app/assets/stylesheets/global.scss:
body {
  padding-top: 4.5rem;
}
Even though this was a newly created file, any .scss file that appears in app/assets/stylesheets/ will get merged into one CSS file that's loaded for every page on the site. It's a Rails convention to put page-specific CSS in their own files for each page, even though it all gets merged together, so that's what we'll do. The home page should look better now:

Screenshot of fixed non-overlapping navbar

We can also verify that the same navbar appears on the other pages:

Screenshot of navbar on monster materials page

Sweet! Navigation is much improved now that we can select any table we want to look at from any page. Those page titles are probably too long to be sustainable as we add more pages, and having a "Home" link in addition to the branding link is redundant; but for now it fills up the navbar nicely. We can condense things later as we need to. What we've gained here is an easy way to add more pages that we can easily navigate to, and it looks pleasing, too. Maybe some color would be nice, but that's easy enough to fiddle with in the CSS at our leisure. Next time, we'll create the big kahuna of the tables: the monster table, and we'll start linking the elements in that table to the other tables we've created.

No comments:

Post a Comment