Skip to content

Rails 6 & Webpacker

Oren Kanner edited this page Nov 5, 2020 · 10 revisions

In order to use this with Rails 6 and Webpacker, a few changes have to be made:

  • Create a pack
  • Change the layout so that the pack is loaded
  • Update the stylesheet
  • Delete the javascript-non-pack

Create a pack

yarn add flatpickr

Create a new pack at app/javascript/packs/administrate.js

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

require("@rails/ujs").start()
require("turbolinks").start()

// The next line you only need if you want ActiveStorage support
require("@rails/activestorage").start()

// The next line you only need if you need channels in administrate
require("channels")

// The next two lines you only need if you want ActionText support
require("trix")
require("@rails/actiontext")

require("../administrate/index")

One level up (in javascript) create a new folder with one file administrate/index.js:

import '../components/table'
import '../components/date_time_picker'

Date & Time picker

This replaces the component currently bundled and works without JavaScript.

Create components/date_time_picker.js:

import flatpickr from 'flatpickr'

function bindDateTimePickers() {
  [...document.querySelectorAll('[data-type="time"]')].forEach((time) => {
    flatpickr(time, {
      enableTime: true,
      enableSeconds: true,
      noCalendar: true,
      altInput: true,
      altFormat: ' h:i:S K',
      dateFormat: 'H:i:S' // H:i
    })
  });

  [...document.querySelectorAll('[data-type="datetime"]')].forEach((time) => {
    flatpickr(time, {
      enableTime: true,
      altInput: true,
      altFormat: 'F J (D), Y - h:i:S K',
      dateFormat: 'Z' // Y-m-d H:i
    })
  });

  [...document.querySelectorAll('[data-type="date"]')].forEach((date) => {
    flatpickr(date, {
      altInput: true,
      altFormat: "F j, Y",
      dateFormat: "Y-m-d"
    })
  });
}

document.addEventListener("turbolinks:load", function () {
  bindDateTimePickers()
})

Table row click

This replaces the current component table.js and works without jquery or the old ujs.

Create components/table.js

function bindTableLinks() {
  const keycodes = { space: 32, enter: 13 }

  function visitDataUrl(event) {
    /** @type {HTMLTableRowElement} */
    const target = event.target.classList.contains("js-table-row")
      ? event.target
      : event.target.closest('.js-table-row')

    if (!target) {
      return
    }

    if (event.type === "click" ||
        event.keyCode === keycodes.space ||
        event.keyCode === keycodes.enter) {

      if (event.target.href) {
        return
      }

      const dataUrl = target.getAttribute("data-url")
      const selection = window.getSelection().toString()
      if (selection.length === 0 && dataUrl) {
        window.location = dataUrl
      }
    }
  }

  const tables = [...document.getElementsByTagName("table")]
  tables.forEach(
    /** @type {HTMLTableElement} */ (table) => {
    table.addEventListener("click", visitDataUrl)
    table.addEventListener("keydown", visitDataUrl)
  })
}

document.addEventListener("turbolinks:load", function() {
  bindTableLinks()
})

Select boxes

There is currently no recommended replacement for selectize.js

Change the layout

Now you need to load this pack in the layout, and stop bundling the old assets.

in app/views/layouts/admin create application.html.erb:

<%#
# Application Layout

This view template is used as the layout
for every page that Administrate generates.

By default, it renders:
- Navigation
- Content for a search bar
  (if provided by a `content_for` block in a nested page)
- Flashes
- Links to stylesheets and JavaScripts
%>

<!DOCTYPE html>
<html lang="<%= I18n.locale %>">
  <head>
    <meta charset="utf-8">
    <meta name="robots" content="noodp, noydir, index, follow">
    <meta name="viewport" content="initial-scale=1">
    <title>
      <%= content_for(:title) || 'Dashboard' %> - <%= Rails.application.class.module_parent_name.titlecase %>
    </title>

    <%= csp_meta_tag %>
    <%= javascript_pack_tag 'administrate', 'data-turbolinks-track': 'reload' %>

    <%= render "stylesheet" %>
    <%= csrf_meta_tags %>

    <meta name="turbolinks-root" content="/admin">
  </head>
  <body>
    <div class="app-container">
      <%= render "navigation" -%>

      <main class="main-content" role="main">
        <%= render "flashes" -%>
        <%= yield %>
      </main>
    </div>

    <div style="display: none; width: 0; height: 0; overflow: hidden; position: absolute">
      <%= render "icons" %>
    </div>
    <%= render "javascript" %>
  </body>
</html>

And one directory up (app/views) create the structure admin/application/_javascript.html.erb:

<%#
# Javascript Partial

This partial imports the necessary javascript on each page.
By default, it includes the application JS,
but each page can define additional JS sources
by providing a `content_for(:javascript)` block.

Administrate::Engine.javascripts.each do |js_path|
 <= echo javascript_include_tag js_path
<% end
%>

<%= yield :javascript %>

<% if Rails.env.test? %>
  <%= javascript_tag do %>
    $.fx.off = true;
    $.ajaxSetup({ async: false });
  <% end %>
<% end %>

As you can see, all this does is load the correct pack in the head and stop including the administrate scripts.

Update stylesheet

In order to work with the flatpickr css, and potentially actionpicker, generate the stylesheets layout via the generator, then open app/assets/stylesheets/administrate/application.scss:

@charset "utf-8";

@import "administrate/reset/normalize";

@import "administrate/library/clearfix";
@import "administrate/library/data-label";
@import "administrate/library/variables";

@import "administrate/base/forms";
@import "administrate/base/layout";
@import "administrate/base/lists";
@import "administrate/base/tables";
@import "administrate/base/typography";

@import "administrate/components/app-container";
@import "administrate/components/attributes";
@import "administrate/components/buttons";
@import "administrate/components/cells";
@import "administrate/components/field-unit";
@import "administrate/components/flashes";
@import "administrate/components/form-actions";
@import "administrate/components/main-content";
@import "administrate/components/navigation";
@import "administrate/components/pagination";
@import "administrate/components/search";

@import "administrate/utilities/text-color";

@import "trix/dist/trix";
@import "selectize";

// We need to override trix.css’s image gallery styles to accommodate the
// <action-text-attachment> element we wrap around attachments. Otherwise,
// images in galleries will be squished by the max-width: 33%; rule.
.trix-content {
  .attachment-gallery {
    > action-text-attachment,
    > .attachment {
      flex: 1 0 33%;
      padding: 0 0.5em;
      max-width: 33%;
    }

    &.attachment-gallery--2,
    &.attachment-gallery--4 {
      > action-text-attachment,
      > .attachment {
        flex-basis: 50%;
        max-width: 50%;
      }
    }
  }

  action-text-attachment {
    .attachment {
      padding: 0 !important;
      max-width: 100% !important;
    }
  }
}

.field-unit--rich-text-area-field {
  .field-unit__field {
    width: 80%;
  }
}

@import "flatpickr/dist/flatpickr.min";

Remove javascript

If done right, the only javascript asset left is the administrate one. If it is still there, remove it from app/assets/javascripts. You don't need this because it's all packed.

Rejoice

That's it. If you want to load the stylesheet through webpacker as well, create a new css pack, and move over the entire administrate directory. Same steps as above but then for the stylesheet 💯