Rails Demo 4: Making things pretty with Bootstrap
Bootstrap is a popular front-end library released by Twitter that makes it easy to add professional styling to our web app. This demo continues the blog app you wrote previously.
The instructions for configuring Rails 6 to use Bootstrap were provided by this video: https://gorails.com/episodes/how-to-use-bootstrap-with-webpack-and-rails.
Step 1: First we need to install Bootstrap into our Rails app.
-
Use
yarnto install thebootstrap,jquery, andpopper.jspackagesyarn add bootstrap jquery popper.js -
Modify
config/webpack/environment.jsso that it looks like this:const { environment } = require('@rails/webpacker') const webpack = require("webpack"); environment.plugins.append("Provide", new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', Popper: ['popper.js', 'default'] })); module.exports = environmentThe “
Provide” plugin makes the$,jQuery, andPoppernames available to Bootstrap. - Create a file named
app/javascript/stylesheets/application.scssthat contains one line:@import "~bootstrap/scss/bootstrap"- You will need to create the
app/javascript/stylesheetsdirectory. - Importing this
scssfile makes various bootstrap CSS variables available to JavaScript code. - Note: This
application.scssfile is different fromapp/assets/stylesheets/application.scss.
- You will need to create the
-
Modify
app/javascript/packs/application.jsso that it looks like this:require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") import "bootstrap" import "../stylesheets/application" document.addEventListener("turbolinks:load", () => { $('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="popover"]').popover(); });import "bootstrap"imports the Bootstrap-related JavaScript code.- The
addEventListenerreacts to a page load and runs the code necessary to make tooltips and popovers work.
-
Add the following line to
app/views/layouts/application.html<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>application.html.erbis the template used by all views.- The erb code you write in your views is inserted by the
yield. - The
stylesheet_pack_tagcauses every page to load the css packaged up by Webpacker.- This package contains the Bootstrap CSS
- Similarly, the
javascript_pack_tagcauses every page to load the JavaScript packaged up by Webpacker
Step 2: Show the various templates / components available with BootStrap:**
Step 3: Set up a Home page
-
Generate a
Homecontrollerrails generate controller Home -
Make sure the controller has an
indexmethod:class HomeController < ApplicationController def index end end -
Add this route to
config/routes.rb:root 'home#index' -
Create the home page template:
views/home/index.html.erb(You don’t need any content yet.)- Copy code from
basicColumns.htmlto verify that the Bootstrap CSS is available and working. (Add some borders into thehome.scssto make it easier to see what’s going on.)
- Copy code from
Step 4: Now Let’s add a Jumbotron to our homepage!
- Download a background image
cdintoapp/assets/images-
run
curl -L https://bit.ly/3bD7QJM --output bees.jpg - The
-Lis important. - (The actual full URL for the bee image is https://raw.githubusercontent.com/jengelsma/CIS658-Winter18-BlogDemo/master/app/assets/images/bees.jpg)
-
Add the following to
views/home/index.html.erb:<div class="jumbotron-fluid"> <div class="container"> <h1>Welcome to my world of bees!</h1> <p>The happiness of the bee and the dolphin is to exist. For man it is to know that and to wonder at it.</p> <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more »</a></p> </div> </div> -
Add the following to
home.scss**.jumbotron-fluid { background-image: asset-url('bees.jpg'); background-size: cover; height: 400px; } .jumbotron-fluid h1 { color: #fff; text-align: center; margin-bottom: 30px; letter-spacing: -1px; font-weight: bold; } .jumbotron-fluid p { color: #fff; text-align: center; margin-bottom: 30px; font-weight: bold; } - To see the bee image, you may have to
- Stop the server
- run
rake tmp:cache:clear - start the server.
- run
- Stop the server
Step 5: Nice! Now let’s call out our most recent articles under the Jumbotron
-
Add the following to
index.html.erb<div class="container-fluid"> <!-- Example row of columns --> <div class="row"> <div class="col-md-4"> <h2>Heading</h2> <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p> <p><a class="btn btn-default" href="#" role="button">View details »</a></p> </div> <div class="col-md-4"> <h2>Heading</h2> <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p> <p><a class="btn btn-default" href="#" role="button">View details »</a></p> </div> <div class="col-md-4"> <h2>Heading</h2> <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p> <p><a class="btn btn-default" href="#" role="button">View details »</a></p> </div> </div> <hr> <footer> <p>© 2018 Company, Inc.</p> </footer> </div> -
In your home controller, implement the index method to do the query.
def index @posts = Post.last(3) end -
Now render these posts using a similar HTML structure to that from Step 1. Replace the hardwired Heading
divs from with this:<% @posts.each do |post| %> <div class="col-md-4"> <h2><%= post.title%></h2> <p><%=post.article.truncate_words(30)%></p> <p> <%= link_to 'Read more »'.html_safe, post, class: "btn btn-default"%> </p> </div> <% end %>
Step 6: Now let’s add a standard nav bar to our app throughout.
-
Create a new partial named
views/layouts/_navigation.html.erband place this code in it<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="#">Bee Blog</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link navbar-dark" href="/">Home <span class="sr-only">(current)</span></a> </li> <li class="nav-item"> <a class="nav-link navbar-dark" href="/authors">Authors</a> </li> <li class="nav-item"> <a class="nav-link navbar-dark" href="/posts">Posts</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle navbar-dark" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Other Sites </a> <div class="dropdown-menu" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="https://www.gvsu.edu">GVSU</a> <a class="dropdown-item" href="https://www.cis.gvsu.edu">CIS</a> <div class="dropdown-divider"></div> <a class="dropdown-item" href="https://www.gatech.edu">Georgia Tech</a> </div> </li> </ul> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav> -
Now invoke this partial in
<%= render 'layouts/navigation' %> <%= yield %>views/layouts/application.html.erb:**
Step 7: Spruce up our Author pages
-
Modify
views/authors/index.html.erbto look like this:<% if !notice.blank? %> <p id="notice" class="alert alert-success"><%= notice %></p> <% end %> <h1>Authors</h1> <table class="table table-hover"> <thead> <tr> <th class="w-25">First Name</th> <th class="w-25">Last Name</th> <th class="w-25">Email</th> <th class="w-25">Actions</th> </tr> </thead> <tbody> <% @authors.each do |author| %> <tr> <td class="w-25"><%= author.fname %></td> <td class="w-25"><%= author.lname %></td> <td class="w-25"><%= author.email %></td> <td class="w-25"> <%= link_to author, class: "btn btn-default btn-sm" do %> Show <% end %> <%= link_to edit_author_path(author), class: "btn btn-default btn-sm" do %> Edit <% end %> <%= link_to author, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-danger btn-sm" do %> Delete <% end %> </td> </tr> <% end %> </tbody> </table> <br/> <%= link_to 'New Author', new_author_path, :class => "btn btn-primary" %> -
Make the form partial for authors pretty as well. (Notice specifically that we’ve added bootstrap classes to the various elements.)
<%= form_with(model: author, local: true) do |form| %> <% if author.errors.any? %> <div id="error_explanation" class="alert alert-danger"> <strong><%= pluralize(author.errors.count, "error") %> prohibited this author from being saved:</strong> <ul> <% author.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="form-group"> <%= form.label :fname , "First Name"%> <%= form.text_field :fname, id: :author_fname, class: "form-control" %> </div> <div class="form-group"> <%= form.label :lname, "Last Name" %> <%= form.text_field :lname, id: :author_lname, class: "form-control" %> </div> <div class="form-group"> <%= form.label :email, "Email" %> <%= form.text_field :email, id: :author_email, class: "form-control" %> </div> <div class="form-group"> <%= form.label :thumbnail, "Profile Image" %> <%= form.file_field :thumbnail, id: :author_thumbnail, class: "form-control-file" %> </div> <div class="actions"> <%= form.submit class: "btn btn-primary" %> <%= link_to 'Cancel', authors_path, class: "btn btn-danger" %> </div> <% end %> -
Since we added a file upload type input, let’s doctor our controller a bit so it can extract the file name. Add these two lines of code at the top of the
createandupdatemethods:thumb_upload = params[:author][:thumbnail] params[:author][:thumbnail] = thumb_upload.original_filename unless thumb_upload.nil?