Installation

You can start by cloning template from github adminLTE and preview localy (it should look the same as preview). You can open documentation localy or online

git clone https://github.com/almasaeed2010/AdminLTE.git
cd AdminLTE
gnome-open index.html
gnome-open documentations/index.html

Latest version is 2.4.2 uses bootstrap 3 and jquery 3. To install to existing rails project you can use bower inside rails and than add admin-lte

bower install --save admin-lte boostrap font-awesome ionicons

I use separated bootstrap (not from admin-lte package) because “Glyphicons” are properly linked. Also I use latest font awesome. There is admin-lte.scss version but not updated.

We can use starter.html for testing, just copy from vendor/assets/bower_components/admin-lte/starter.html select <div class="wrapper"> and put on your page. CSS and js will we using sprockets and compiled in one big application.css/.js file.

When using webpacker you can install with

yarn add admin-lte jquery

# Heroku will automatically run "build" script so you can add to package.json
 "scripts": {
   "build": "yarn --cwd node_modules/admin-lte run install"
 },
# but I have some problems linking packages under plugins so I link them
# directly

# app/javascript/packs/application.js
import 'stylesheet/application'

# import plugins directly, not 'admin-lte/plugins/select2/js/select2'
import "select2/dist/js/select2"


# app/javascript/stylesheet/application.scss
@import "~admin-lte/plugins/select2/css/select2";

I think is similar to
# app/javascript/stylesheet/application.css
@import 'admin-lte'

Since adminlte is using less we will use two wrappers.

// app/assets/application.css
/*
 * load bootstrap here since importing in less will use css import file command
 *= require bootstrap/dist/css/bootstrap.css
 *= require application_less_wrapper
 *= require application_scss_wrapper
 */
# Gemfile, below sass-rails
# Also need less for adminlte
gem 'less-rails', '~> 3.0.0'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer'
# config/initializers/assets.rb

# We need to add also subpaths admin-lte and admin-lte/skins since there is
# relative reference for bootstrap in skins less files
Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets', 'bower_components')
Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets', 'bower_components', 'admin-lte')
Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets', 'bower_components', 'admin-lte', 'skins')
Rails.application.config.assets.precompile << /\.(?:svg|eot|woff|ttf)$/

Copy some header from starter.html to your layout file

# app/views/layouts/applicantion.html.erb
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title><%= content_for?(:page_title) ? yield(:page_title) : default_page_title %></title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->

    <!-- Google Font -->
    <link rel="stylesheet"
          href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
  </head>

  <body class="hold-transition skin-blue sidebar-mini <%= sidebar_collapse %>">
    <div class="wrapper">

      <!-- Main Header -->
      <header class="main-header">
        <!-- Logo -->
        <a href="/" class="logo">
          <!-- mini logo for sidebar mini 50x50 pixels -->
          <span class="logo-mini">
            <b>ST</b>
          </span>
          <!-- logo for regular state and mobile devices -->
          <span class="logo-lg">
            <b>Stuff</b>Timesheet
          </span>
        </a>

        <!-- Header Navbar -->
        <nav class="navbar navbar-static-top" role="navigation">
          <!-- Sidebar toggle button-->
          <a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button" data-server-url="<%= preferences_path %>">
            <span class="sr-only">Toggle navigation</span>
          </a>
          <!-- Navbar Right Menu -->
          <div class="navbar-custom-menu">
            <ul class="nav navbar-nav">
              <!-- Messages: style can be found in dropdown.less-->
              <li class="dropdown messages-menu">
                <!-- Menu toggle button -->
                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                  <i class="fa fa-envelope-o"></i>
                  <span class="label label-success">4</span>
                </a>
                <ul class="dropdown-menu">
                  <li class="header">You have 4 messages</li>
                  <li>
                    <!-- inner menu: contains the messages -->
                    <ul class="menu">
                      <li><!-- start message -->
                        <a href="#">
                          <div class="pull-left">
                            <!-- User Image -->
                            <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">
                          </div>
                          <!-- Message title and timestamp -->
                          <h4>
                            Support Team
                            <small><i class="fa fa-clock-o"></i> 5 mins</small>
                          </h4>
                          <!-- The message -->
                          <p>Why not buy a new awesome theme?</p>
                        </a>
                      </li>
                      <!-- end message -->
                    </ul>
                    <!-- /.menu -->
                  </li>
                  <li class="footer"><a href="#">See All Messages</a></li>
                </ul>
              </li>
              <!-- /.messages-menu -->

              <!-- Notifications Menu -->
              <li class="dropdown notifications-menu">
                <!-- Menu toggle button -->
                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                  <i class="fa fa-bell-o"></i>
                  <span class="label label-warning">10</span>
                </a>
                <ul class="dropdown-menu">
                  <li class="header">You have 10 notifications</li>
                  <li>
                    <!-- Inner Menu: contains the notifications -->
                    <ul class="menu">
                      <li><!-- start notification -->
                        <a href="#">
                          <i class="fa fa-users text-aqua"></i> 5 new members joined today
                        </a>
                      </li>
                      <!-- end notification -->
                    </ul>
                  </li>
                  <li class="footer"><a href="#">View all</a></li>
                </ul>
              </li>
              <!-- Tasks Menu -->
              <li class="dropdown tasks-menu">
                <!-- Menu Toggle Button -->
                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                  <i class="fa fa-flag-o"></i>
                  <span class="label label-danger">9</span>
                </a>
                <ul class="dropdown-menu">
                  <li class="header">You have 9 tasks</li>
                  <li>
                    <!-- Inner menu: contains the tasks -->
                    <ul class="menu">
                      <li><!-- Task item -->
                        <a href="#">
                          <!-- Task title and progress text -->
                          <h3>
                            Design some buttons
                            <small class="pull-right">20%</small>
                          </h3>
                          <!-- The progress bar -->
                          <div class="progress xs">
                            <!-- Change the css width attribute to simulate progress -->
                            <div class="progress-bar progress-bar-aqua" style="width: 20%" role="progressbar"
                                 aria-valuenow="20" aria-valuemin="0" aria-valuemax="100">
                              <span class="sr-only">20% Complete</span>
                            </div>
                          </div>
                        </a>
                      </li>
                      <!-- end task item -->
                    </ul>
                  </li>
                  <li class="footer">
                    <a href="#">View all tasks</a>
                  </li>
                </ul>
              </li>
              <!-- User Account Menu -->
              <li class="dropdown user user-menu">
                <!-- Menu Toggle Button -->
                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                  <!-- The user image in the navbar-->
                  <img src="dist/img/user2-160x160.jpg" class="user-image" alt="User Image">
                  <!-- hidden-xs hides the username on small devices so only the image appears. -->
                  <span class="hidden-xs">Alexander Pierce</span>
                </a>
                <ul class="dropdown-menu">
                  <!-- The user image in the menu -->
                  <li class="user-header">
                    <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">

                    <p>
                      Alexander Pierce - Web Developer
                      <small>Member since Nov. 2012</small>
                    </p>
                  </li>
                  <!-- Menu Body -->
                  <li class="user-body">
                    <div class="row">
                      <div class="col-xs-4 text-center">
                        <a href="#">Followers</a>
                      </div>
                      <div class="col-xs-4 text-center">
                        <a href="#">Sales</a>
                      </div>
                      <div class="col-xs-4 text-center">
                        <a href="#">Friends</a>
                      </div>
                    </div>
                    <!-- /.row -->
                  </li>
                  <!-- Menu Footer-->
                  <li class="user-footer">
                    <div class="pull-left">
                      <a href="#" class="btn btn-default btn-flat">Profile</a>
                    </div>
                    <div class="pull-right">
                      <a href="#" class="btn btn-default btn-flat">Sign out</a>
                    </div>
                  </li>
                </ul>
              </li>
              <!-- Control Sidebar Toggle Button -->
              <li>
                <a href="#" data-toggle="control-sidebar"><i class="fa fa-gears"></i></a>
              </li>
            </ul>
          </div>
        </nav>
      </header>
      <!-- Left side column. contains the logo and sidebar -->
      <aside class="main-sidebar">

        <!-- sidebar: style can be found in sidebar.less -->
        <section class="sidebar">

          <!-- Sidebar user panel (optional) -->
          <div class="user-panel">
            <div class="pull-left image">
              <img src="dist/img/user2-160x160.jpg" class="img-circle" alt="User Image">
            </div>
            <div class="pull-left info">
              <p>Alexander Pierce</p>
              <!-- Status -->
              <a href="#"><i class="fa fa-circle text-success"></i> Online</a>
            </div>
          </div>

          <!-- search form (Optional) -->
          <form action="#" method="get" class="sidebar-form">
            <div class="input-group">
              <input type="text" name="q" class="form-control" placeholder="Search...">
              <span class="input-group-btn">
                  <button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
                  </button>
                </span>
            </div>
          </form>
          <!-- /.search form -->

          <!-- Sidebar Menu -->
          <ul class="sidebar-menu" data-widget="tree">
            <li class="header">HEADER</li>
            <!-- Optionally, you can add icons to the links -->
            <li class="active"><a href="#"><i class="fa fa-link"></i> <span>Link</span></a></li>
            <li><a href="#"><i class="fa fa-link"></i> <span>Another Link</span></a></li>
            <li class="treeview">
              <a href="#"><i class="fa fa-link"></i> <span>Multilevel</span>
                <span class="pull-right-container">
                    <i class="fa fa-angle-left pull-right"></i>
                  </span>
              </a>
              <ul class="treeview-menu">
                <li><a href="#">Link in level 2</a></li>
                <li><a href="#">Link in level 2</a></li>
              </ul>
            </li>
          </ul>
        </section>
      </aside>

      <!-- Content Wrapper. Contains page content -->
      <div class="content-wrapper">
        <!-- Content Header (Page header) -->
        <section class="content-header">
          <h1>
            <%= yield :page_header %>
            <small><%= yield :page_description %></small>
          </h1>
          <ol class="breadcrumb">
            <% get_breadcrumb_list.each_with_index do |(text, link), i| %>
              <li class="<%= 'active' if i == get_breadcrumb_list.length - 1 %>">
                <% if link.present? %>
                  <%= link_to link do %>
                    <% if i == 0 %>
                      <i class="fa fa-dashboard"></i>
                    <% end %>
                    <%= text %>
                  <% end %>
                <% else %>
                  <% if i == 0 %>
                    <i class="fa fa-dashboard"></i>
                  <% end %>
                  <%= text %>
                <% end %>
              </li>
            <% end %>

          </ol>
        </section>

        <!-- Main content -->
        <section class="content">

          <% if notice %>
            <div class="hide-to-up"><%= notice %></div>
            <script>
              flash_notice('<%= j notice %>');
            </script>
          <% end %>

          <% if alert %>
            <div class="hide-to-up"><%= alert %></div>
            <script>
              flash_alert('<%= j alert %>');
            </script>
          <% end %>

          <!-- Your Page Content Here -->
          <%= yield %>

        </section>
        <!-- /.content -->
      </div>
      <!-- /.content-wrapper -->

      <!-- Main Footer -->
      <footer class="main-footer">
        <!-- To the right -->
        <div class="pull-right hidden-xs">
          Anything you want
        </div>
        <!-- Default to the left -->
        <strong>Copyright &copy; 2016 <a href="#">Company</a>.</strong> All rights reserved.
      </footer>

      <!-- Control Sidebar -->
      <aside class="control-sidebar control-sidebar-dark">
      </aside>
      <!-- /.control-sidebar -->
      <!-- Add the sidebar's background. This div must be placed
           immediately after the control sidebar -->
      <div class="control-sidebar-bg"></div>
    </div>
    <script>
      <%= yield :javascript %>
    </script>

  </body>
</html>

Some helpers

# app/helpers/page_helper.rb
module PageHelper
  def sidebar_collapse
    if current_user&.sidebar_collapse
      'sidebar-collapse'
    end
  end

  def preferences_path
    '/'
  end

  # use in view: page_title "Home"
  def page_title(title)
    # this will add both page title and header below topnav
    content_for(:page_title) { title }
    content_for(:page_header) { title }
  end

  def default_page_title
    add_location_to_title(
      "Internet Subscriber Management"
    )
  end

  # use in view: page_description "Dashboard"
  def page_description(description)
    content_for(:page_description) { description.html_safe }
  end

  # use in view: breadcrumb "Home": root_path, "Dashboard": nil
  def breadcrumb(list)
    @breadcrumb = list
  end

  def get_breadcrumb_list
    @breadcrumb || []
  end
end
// app/assets/stylesheets/application_less_wrapper.less
@import "Ionicons/less/ionicons";
// AdminLTE.less stuff need to be after bootstrap which includes normalize.scss
@import "AdminLTE/build/less/AdminLTE";
@import "AdminLTE/build/less/skins/skin-blue";

// default sidebar width is 230px
@sidebar-width: 170px;
// app/assets/stylesheets/application_scss_wrapper.scss
// on production all assets gets fingerprint, but here we can define only path
// $fa-font-path: 'font-awesome/fonts';
// so use Bootstrap CDN for font files
$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.7.0/fonts" !default;
@import 'font-awesome/scss/font-awesome';
// app/assets/javascript/application_adminlte.js
//= require rails-ujs
//= require turbolinks
//= require_tree .
//= require js.cookie
//= require jstz
//= require browser_timezone_rails/set_time_zone
//
// AdminLTE stuff
//= require jquery/dist/jquery.min
//= require bootstrap/dist/js/bootstrap.min
//= require admin-lte/dist/js/adminlte

Also you need to exclude those _adminlte files in original sprockets:

// app/assets/stylesheets/application.scss
 *= stub application_adminlte

// app/assets/javascript/application.js
//= stub application_adminlte

Stubing in sprockets is recursive so if you need to include libraries (for example jquery_ujs) in both layouts it will stub that libraries also. Also if use use require_tree . than you can inadvertently add some libs to some layout which you have not consider in that moment.

Better approach is to create new folder app/assets/adminlte and add it to asset pipeline paths.

Since asset pipeline ignore first subfolders you need to take care to name files differently when you create new scss and coffee files. I solved that by adding suffix _adminlte.

Notify if template is missing

You can notify if some of the translated templates does not exists using get template name and add notification

# config/initializers/notify_if_adminlte_template_is_not_present.rb
# getting current template is not possible in rails
# https://github.com/rails/rails/issues/4876
# patch taken from
# http://stackoverflow.com/questions/4973699/rails-3-find-current-view-while-in-the-layout/8310881#8310881
class ActionView::TemplateRenderer
  alias_method :_render_template_original, :render_template

  def render_template(template, layout_name = nil, locals = {})
    if I18n.locale == :in &&
      template.inspect.end_with?(".in.html.erb") &&
      template.inspect.end_with?(".in.js.erb") &&
      template.inspect.end_with?(".in.json.jbuilder") &&
      template.inspect.start_with?("app/views/devise/mailer") &&
      template.inspect.start_with?("app/views/mailers") &&
      template.inspect.start_with?("app/views/api") &&
      template.inspect.start_with?("rails/mailers/email") &&
      template.inspect != "text template".freeze && # this is when send_data
      template.inspect != "html template".freeze # this is when render html
      cache_key = "adminlte_#{template.virtual_path}"
      if Rails.cache.fetch cache_key
        # already notified
      else
        Rails.cache.write cache_key, Time.now
        UserMailer.internal_notification(
          "Adminlte Template does not exists for #{template.virtual_path}",
          template
        ).deliver_now
      end
    end
    result = _render_template_original( template, layout_name, locals)
    result
  end
end

Turbolinks

https://github.com/almasaeed2010/AdminLTE/issues/563

$(document).on 'turbolinks:load', ->
  console.log 'turbolinks:load'
  $(window).trigger('resize')

Configuration

Plugins

AdminLTE is based on Bootstrap 3 and jQuery 1.11+. It contains a lot of plugins as you can see in their documentation

  • iCheck for checkboxes

    bower install icheck --save
    
    /* app/assets/adminlte/application_adminlte.scss
    *= require iCheck/skins/square/blue
    
    // app/assets/adminlte/application_adminlte.js
    //= require iCheck/icheck.min
    

    Note that when you run bower install than some version of jquery will be installed and it will overwrite existing jquery if you have used it (in old or new application.scss layout file).

    If you receive error similar to Refused to execute script from because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled that means that asset path is bad, and rails can not find that precompiled asset. Sometime this happens only on production env.

  • datatables is used for search and ordering on client side
  • bootstrap-datepicker
  • bootstrap-daterangepicker

Premium plugins

You can check premium version: adminlte which usually use free plugins which you can download separatelly. It looks more professional.

Options

It contains nice features (as stated in their blog)

  • toolbar can be autoexpand using js options

You can use colors with background bg-red bg-blue … or for text text-red, text-blue… Colors could be also for components. For example box could be box-default, box-primary, box-info, box-success, box-danger and box-warning.

You can collapse box in javascript by calling $.AdminLTE.boxWidget.collapse($('#box-id')); but it is better to write custom code.

rails bootstrap forms works nice with template, for example registration form:

<%# app/views/devise/registrations/new.html.erb %>
<%= bootstrap_form_for resource, as: resource_name, url: registration_path(resource_name) do |f| %>
  <%= f.email_field :email, autofocus: true, placeholder: "Email", skip_label: true, icon: 'user' %>
  <%= f.password_field :password, class: 'form-control', placeholder: 'Password', skip_label: true, icon: 'lock' %>
  <%= f.password_field :password_confirmation, class: 'form-control', placeholder: 'Password Confirmation', skip_label: true, icon: 'lock' %>
  <button type="submit" class="btn btn-primary btn-block btn-flat" data-disable-with="Processing...">Sign up</button>
<% end %>
<%= render "devise/shared/links" %>

Less source

Since AdminLTE is build using less, you can set variables to customize colors and sizes.

// app/assets/adminlte/stylesheets/less_wrapper_adminlte.less
// AdminLTE stuff need to be after bootstrap (which includes normalize.scss)
@import "AdminLTE/build/less/AdminLTE";
@import "AdminLTE/build/less/skins/skin-blue";

@sidebar-width: 170px;

CRUD and ajax

My prefered setup is to use ajax for edit/update form, use modal for create (with prefilled form, without ajax, since in case of success it should redirect).

Using with existing template

To add new template while keeping existing you can use different layout base on locale. You can set new layout with option ?layout=true So if you add param ?layout=true it will render index.in.html.erb variant using new layout and put that in session (so all form submissions also use new layout). You can disable new_layout with ?layout=false. If you have locale files (probably devise.en.yml) than you need to copy to :in

# app/controllers/application_controller.rb
  layout proc { |controller| controller.new_layout? ? 'adminlte' : 'application' }
  before_filter :set_locale_for_layout

  def new_layout?
    I18n.locale == :rs
  end

  def set_locale_for_layout
    if session[:layout] == true
      if params[:layout] == "false"
        # disable new layout
        session[:layout] = false
        I18n.locale = 'en'
        logger.debug "layout became false"
      else
        logger.debug "layout still true"
        I18n.locale = 'rs'
      end
    else
      # session[:layout] == false
      if params[:layout] == "true"
        # enable new layout
        session[:layout] = true
        logger.debug "layout became true"
        I18n.locale = 'rs'
      else
        I18n.locale = 'rs'
        logger.debug "layout still false"
      end
    end
  end

Also we need to make changes on other places

# config/application.rb
    config.i18n.available_locales = [:en, :rs]

# config/locales/en.yml
# config/locales/devise.en.yml
en: &default
rs:
  <<: *default

# app/views/pages/sample_page.in.html.erb
I'm from adminlte layout


<!-- above Main content in app/views/layouts/adminlte.html.erb -->
    <div class="new-layout-switch">
      <%= link_to "Go Back To Old Layout", params.merge(layout: false) %>
    </div>

// app/assets/stylesheets/application_adminlte.scss
.m-b-10 {
  margin-bottom: 10px;
}

// similar to pull-right just without float
.text-align-right {
  text-align: right;
}

.new-layout-switch {
  position: absolute;
  padding-left: 4px;
  top: 50px;
  right: 0px;
  border-bottom-left-radius: 5px;
  background: #3C8DBC;
  a {
    color: white;
  }
}