Skip to content

Instantly share code, notes, and snippets.

@secretpray
Last active October 31, 2022 18:20
Show Gist options
  • Save secretpray/85a526583c763fd8a399c464820740de to your computer and use it in GitHub Desktop.
Save secretpray/85a526583c763fd8a399c464820740de to your computer and use it in GitHub Desktop.
Add bootstrap to Rails 7 with cssbundling

Add bootstrap to Rails 7 with cssbundling

ruby -v

-> 3+

rails -v

-> 7+

rails new app_name -d postgresql -T
cd app_name
./bin/bundle add cssbundling-rails
./bin/importmap pin bootstrap --from jsdelivr
./bin/rails css:install:bootstrap

optionaly (for development only)

bundle add faker --group "development"

Add to package.json

"scripts": {
  "build:css": "sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules"
}

Add customized_bootstrap.scss (not necessary section)

touch app/assets/stylesheets/customized_bootstrap.scss

Import customized_bootstrap.scss (prepend)

echo "@import 'customized_bootstrap';" | cat - app/assets/stylesheets/application.bootstrap.scss | tee app/assets/stylesheets/application.bootstrap.scss

Start application

./bin/rails g controller home index
./bin/rails db:drop db:create db:migrate db:seed
./bin/dev
@secretpray
Copy link
Author

secretpray commented May 9, 2022

Add tooltip, popover, toast support

  1. app/javascript/application.js (auto start)
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import * as bootstrap from "bootstrap"
window.process = { env: { NODE_ENV: "#{Rails.env}" } }

var toastElList = [].slice.call(document.querySelectorAll('.toast'))
var toastList = toastElList.map(function(toastEl) {
  return new bootstrap.Toast(toastEl).show() // No need for options; use the default options
})

var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
  return new bootstrap.Tooltip(tooltipTriggerEl)
})

var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
  return new bootstrap.Popover(popoverTriggerEl)
})

2.1) Toast Stimulus controllers (not necessary - for separate action only)
app/javascript/controllers/toast_controller.js

import { Controller } from "@hotwired/stimulus"
import * as bootstrap from 'bootstrap'

// Connects to data-controller="toast"
export default class extends Controller {
  connect() {
    // console.log('Use after flash is added!!!')
    //   const toaster = new bootstrap.Toast(this.element, {
    //     delay: 5000,
    //     animation: true,
    //     autohide: true
    //   })
    //   toaster.show()
    // }
  }

// manual call Toast
  showToast() {
    const toastEl = document.querySelector('#liveToast')
    const toaster = new bootstrap.Toast(toastEl, {
      delay: 5000,
      animation: true,
      autohide: true
    })
    toaster.show()
  }
}

2.2) Modal Stimulus controllers (not necessary - for separate action only)

import { Controller } from "@hotwired/stimulus"
import * as bootstrap from 'bootstrap'

// Connects to data-controller="modal"
export default class extends Controller {
  connect(event) {
    console.log('event', event)
    this.modal = new bootstrap.Modal(this.element, {
      keyboard: false
    })
    this.modal.show()
  }

  disconnect() {
    this.modal.hide()
  }
}

@secretpray
Copy link
Author

Sample html.erb (for test Bootstrap components only)
app/views/home/index.html.erb

<div class="mt-5 alert alert-warning alert-dismissible fade show" role="alert">
  <strong>Holy guacamole!</strong> You should check in on some of those fields below.
  <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>

<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">
  Tooltip on top
</button>

<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
  Launch demo modal
</button>

<h1>Welcome, this is the home page</h1>  
  
<button type="button" 
        class="btn btn-lg btn-danger" 
        data-bs-toggle="popover" 
        title="Popover title" 
        data-bs-content="Amazing content, right ?">
        Click to toggle popover
</button>  

<button type="button" 
        class="btn btn-primary" 
        id="liveToastBtn"
        data-bs-toggle="toast"
        data-bs-target="#liveToast"
        data-controller="toast"
        data-action="click->toast#showToast">Show live toast</button>

<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 11">
  <div id="liveToast" 
       class="toast" 
       role="alert" 
       aria-live="assertive" 
       aria-atomic="true"
       data-controller="toast"
       data-toast-target='toastZero'>
    <div class="toast-header">
      <img src="..." class="rounded me-2" alt="...">
      <strong class="me-auto">Bootstrap</strong>
      <small>11 mins ago</small>
      <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
    </div>
    <div class="toast-body">
      Hello, world! This is a toast message.
    </div>
  </div>
</div>

<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">
  Tooltip on top
</button>
<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="right" title="Tooltip on right">
  Tooltip on right
</button>
<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tooltip on bottom">
  Tooltip on bottom
</button>
<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="left" title="Tooltip on left">
  Tooltip on left
</button>

<div class="accordion mt-5" id="accordionExample">
  <div class="accordion-item">
    <h2 class="accordion-header" id="headingOne">
      <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
        Accordion Item #1
      </button>
    </h2>
    <div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
      <div class="accordion-body">
        <strong>This is the first item's accordion body.</strong> It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.
      </div>
    </div>
  </div>
  <div class="accordion-item">
    <h2 class="accordion-header" id="headingTwo">
      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
        Accordion Item #2
      </button>
    </h2>
    <div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
      <div class="accordion-body">
        <strong>This is the second item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.
      </div>
    </div>
  </div>
  <div class="accordion-item">
    <h2 class="accordion-header" id="headingThree">
      <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
        Accordion Item #3
      </button>
    </h2>
    <div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
      <div class="accordion-body">
        <strong>This is the third item's accordion body.</strong> It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the <code>.accordion-body</code>, though the transition does limit overflow.
      </div>
    </div>
  </div>
</div>


<div id="carouselExampleCaptions" class="carousel slide mb-5" data-bs-ride="carousel">
  <div class="carousel-indicators">
    <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
    <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="1" aria-label="Slide 2"></button>
    <button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="2" aria-label="Slide 3"></button>
  </div>
  <div class="carousel-inner">
    <div class="carousel-item active">
      <img src="<%= Faker::LoremFlickr.image(size: "100x100", search_terms: ['sports', 'fitness']) %>" class="d-block w-100" alt="Image 1">
      <div class="carousel-caption d-none d-md-block">
        <h5>First slide label</h5>
        <p>Some representative placeholder content for the first slide.</p>
      </div>
    </div>
    <div class="carousel-item">
      <img src="<%= Faker::LoremFlickr.image(size: "100x100", search_terms: ['sports', 'fitness']) %>" class="d-block w-100" alt="Image 2">
      <div class="carousel-caption d-none d-md-block">
        <h5>Second slide label</h5>
        <p>Some representative placeholder content for the second slide.</p>
      </div>
    </div>
    <div class="carousel-item">
      <img src="<%= Faker::LoremFlickr.image(size: "100x100", search_terms: ['sports', 'fitness']) %>" class="d-block w-100" alt="Image 3">
      <div class="carousel-caption d-none d-md-block">
        <h5>Third slide label</h5>
        <p>Some representative placeholder content for the third slide.</p>
      </div>
    </div>
  </div>
  <button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="prev">
    <span class="carousel-control-prev-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Previous</span>
  </button>
  <button class="carousel-control-next" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="next">
    <span class="carousel-control-next-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Next</span>
  </button>
</div>


<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        ...
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

@MtnBiker
Copy link

Hi. Ran across your trials here.

I'm wondering if you know how to add .css from a node_module? For example: node_modules/leaflet/dist/leaflet.css. Could copy into app/assets/stylesheets/ but seems like there should be a more robust way. If the node_module changes the css would not be current.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment