Introducing ReUI:Open-source UI components and apps built with React, Next.js and Tailwind CSS
Browse ReUI

My progress on using Metronic 9 with Rails 8 Beta 1


Hi! I'm looking forward to seeing an official Rails 8 integration guide for Metronic 9, but in the meantime, I've made quite a bit of progress doing it on my own.

Maybe my progress can help others or give KeenThemes a head start on official instructions. Just a warning: I'm a novice web app developer who just kind of does this as a hobby.

Rails 8 makes major changes over previous versions, making things much simpler. The asset pipeline is now Propshaft, which is very simple. And by default it uses Import Maps, so you don't have to bother with Webpack, Yarn, npm, etc.

So, I wanted to build a Metronic 9 / Rails 8 app using the defaults.

For at least a few days, you can see a small proof-of-concept app here: https://alpha.default-forwarding.net
Scroll between the two pages using the Page 1 and Page 2 links under Static Rails Pages.

The current major problem is that Turbo and the Metronic Javascript don't always work well together. I've done some odd hacks inspired by other posts in this forum (about Metronic 8), but right now:
1. On desktop: The sidebar collapse doesn't really work after navigating between pages. It resets itself so you can still navigate, but the collapsed state is wiped out.
2 On mobile: The sidebar drawer stops working after navigating to a new page. So far, I can't figure out how to fix that. Until I do, this isn't usable in the real world.

Steps to reproduce:
Install and build Metronic using the Installation instructions. I did all of that under ~/projects/html/metronic-v9.0.6/metronic-tailwind-html/
We'll be using files under the dist folder, not the src folder.

Create a new Rails 8 app. Mine was under ~/projects/rails8/
Make sure you have the Beta version of Rails, if the official version is out yet:
gem install -v 8.0.0.beta1 rails

Install a new app (I called mine "met3") with Tailwind pre-installed, using the Main branch of Rails (again, unless the official version is out):
rails new met3 --main --css tailwind
cd met3/

We'll need at least two static pages to test Turbo:
rails generate controller Pages home home2
And set the root by adding this to config/routes.rb:
root "pages#home"

Copy a bunch of files from ~/projects/html/metronic-v9.0.6/metronic-tailwind-html/ to ~/projects/rails8/met3/:

from: dist/assets/vendors/apexcharts/apexcharts.css to: app/assets/stylesheets/apexcharts.css
from: dist/assets/vendors/keenicons/styles.bundle.css to: app/assets/stylesheets/styles.bundle.css
from: dist/assets/css/styles.css to: app/assets/stylesheets/styles.css
from: dist/assets/vendors/apexcharts/apexcharts.min.js to: vendor/javascript/apexcharts.min.js
from: dist/assets/js/layouts/demo1.js to: vendor/javascript/demo1.js
from: dist/assets/js/core.bundle.js to: vendor/javascript/core.bundle.js
from: dist/assets/vendors/keenicons/fonts/keenicons-duotone.svg to: app/assets/fonts/keenicons-duotone.svg
from: dist/assets/vendors/keenicons/fonts/keenicons-duotone.ttf to: app/assets/fonts/keenicons-duotone.ttf
from: dist/assets/vendors/keenicons/fonts/keenicons-duotone.woff to: app/assets/fonts/keenicons-duotone.woff
from: dist/assets/vendors/keenicons/fonts/keenicons-filled.svg to: app/assets/fonts/keenicons-filled.svg
from: dist/assets/vendors/keenicons/fonts/keenicons-filled.ttf to: app/assets/fonts/keenicons-filled.ttf
from: dist/assets/vendors/keenicons/fonts/keenicons-filled.woff to: app/assets/fonts/keenicons-filled.woff
from: dist/assets/vendors/keenicons/fonts/keenicons-outline.svg to: app/assets/fonts/keenicons-outline.svg
from: dist/assets/vendors/keenicons/fonts/keenicons-outline.ttf to: app/assets/fonts/keenicons-outline.ttf
from: dist/assets/vendors/keenicons/fonts/keenicons-outline.woff to: app/assets/fonts/keenicons-outline.woff
from: dist/assets/vendors/keenicons/fonts/keenicons-solid.svg to: app/assets/fonts/keenicons-solid.svg
from: dist/assets/vendors/keenicons/fonts/keenicons-solid.ttf to: app/assets/fonts/keenicons-solid.ttf
from: dist/assets/vendors/keenicons/fonts/keenicons-solid.woff to: app/assets/fonts/keenicons-solid.woff
from: dist/assets/media to: app/assets/images/media


Time to modify some files:
From ~/projects/html/metronic-v9.0.6/metronic-tailwind-html/tailwind.config.js copy just these two sections:

darkMode: "class",
theme: { ....bunch of stuff... }


and insert into ~/projects/rails8/met3/config/tailwind.config.js.
Note, you just want the darkMode and theme sections. In my testing, there was no need to copy anything from the content, safelist, or plugins sections.

Next, we're going to bring in the Metronic Javascript, and we're going to use Import Maps to do it. Add the following to config/importmap.rb:


pin "core.bundle", to: "core.bundle.js"
pin "apexcharts.min", to: "apexcharts.min.js"
pin "demo1", to: "demo1.js"


Add the following to the end of app/javascript/application.js :

import "core.bundle"
import "apexcharts.min"
import "demo1"


What about the CSS files? Rails 8 Propshaft seems to just take care of inserting them for us. There's nothing we need to do.

But we do need to modify the app/assets/stylesheets/styles.bundle.css file a little bit.
In order for Propshaft to properly intercept the location of the font files, we need to find the sections that look like this:

src:
url("fonts/keenicons-duotone.ttf?gcn9yo") format("truetype"),
url("fonts/keenicons-duotone.woff?gcn9yo") format("woff"),
url("fonts/keenicons-duotone.svg?gcn9yo#keenicons-duotone") format("svg");

and get rid of the fonts/ part, leaving it looking like this:

src:
url("keenicons-duotone.ttf?gcn9yo") format("truetype"),
url("keenicons-duotone.woff?gcn9yo") format("woff"),
url("keenicons-duotone.svg?gcn9yo#keenicons-duotone") format("svg");


Now, we start building views. First, make your app/views/layouts/application.html.erb file look something like this:

<!DOCTYPE html>
<html class="h-full" data-theme="true" data-theme-mode="light" lang="en">
<head>
<title><%= content_for(:title) || "Met3" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= yield :head %>
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
<link rel="icon" href="/icon.png" type="image/png">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"/>
<%# Includes all stylesheet files in app/assets/stylesheets %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body class="antialiased flex h-full text-base text-gray-700 [--tw-page-bg:#fefefe] [--tw-page-bg-dark:var(--tw-coal-500)] demo1 sidebar-fixed header-fixed bg-[--tw-page-bg] dark:bg-[--tw-page-bg-dark]">
<%= yield %>
</body>
</html>

There's some improvements that can be made there, but it's a good starting point.

Note, there is no block of Javascript to make the theme switcher button work, like there is in the documentation or sample HTML files. That functionality seems to already be baked into core.bundle.js, and it just works.

Next, we need two working view files. This is just a proof of concept, but here is where you would normally start building out Rails partials.

Copy HTML out of the HTML files, and into your app/views/pages/home.html.erb and app/views/pages/home2.html.erb files.

I copied from <!-- Page --> to <!-- End of Page --> out of metronic-tailwind-html/dist/html/demo1/public-profile/empty.html and into app/views/pages/home.html.erb - Almost 5,000 lines of code.
Then I did the same from metronic-tailwind-html/dist/html/demo1/public-profile/activity.html to app/views/pages/home2.html.erb.

At this point, you can run the Rails development server bin/dev and you should see a Metronic page with broken images at HTTP://localhost:3000 .

Unfortunately, to fix the images, all the img tags need to be converted into rails image_tag helper. And there's 130 in each of the two view files.
I ended up writing a Python script to do almost all of it. I'll try and post that script below.

In general, you want to find things like

<img class="max-h-[25px] w-full" src="assets/media/app/mini-logo.svg"/>

and turn it into

<%= image_tag "media/app/mini-logo.svg", class: "max-h-[25px] w-full" %>

Note that "assets/" gets removed from the path.

There's a couple of parts where there is content between opening and closing img tags, like
<img src="something.png">lots of tag</img>

Those should be turned into image_tag do blocks:

<%= image_tag "something.png", class: "whatever" do %>lots of tags<% end %>


Somewhere on the two pages, you want to add Rails link_to helpers so you can navigate between them. For example:
<%= link_to pages_home_path %>
<%= link_to pages_home2_path %>

Finally, we get to where the Javascript starts to have problems with Turbo. A bunch of Metronic functions stop working when you navigate between the two pages. So far, the best I've been able to do to get them working consistently is to add this block to the very end of the app/javascript/application.js file:

document.addEventListener("turbo:load", function() {
KTComponents.init()
KTMenu.initHandlers()
KTDropdown.initHandlers();
KTTooltip.initHandlers();
});

KTComponents.init()
KTMenu.initHandlers()
KTDropdown.initHandlers();
KTTooltip.initHandlers();


Search this forum for "Rails" "Turbo" and "Metronic". You should find two posts that talk a bit about why this seems to be necessary.

And that's as far as I've gotten so far. Hopefully, someone has some ideas about the Turbo and Metronic interaction, and hopefully this saves someone some time.


Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(

Replies (9)


Hi Ian Adams

Thanks for sharing this update! That explains a lot about the reinitialization issues with Turbo and Metronic 9.

Thanks again for the detailed explanation, it's really helpful!



Update for anyone still having Turbo problems with Metronic 9 -

I discovered tonight that I still having a problem where modals wouldn't work after a turbo load, so I took another look at things.
If you read my notes about javascript below, you know you need to reinitialize all the KT components after turbo load. Even though I thought I kept re-initializing modals, they were never actually reinitializing...

It turns out there are eight components that have variables that get set to "true" at the first initialization, and those variables prevent future re-initialization.

So, ignore that javascript fix I described a couple weeks ago, and use this much simpler one instead. So far it seems to have solved all my Metronic 9/Turbo problems:

app/javascript/application.js

// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"

// Import Metronic 9 JS
import "core.bundle"
import "apexcharts.min"

// Reinitialize all Metronic compnents after Turbo has loaded
document.addEventListener("turbo:load", function() {
// After turbo has loaded, set several KT_***_INITIALIZED variables to false, and re-run the KTComponents.init() function.
// Some, or all, of these initializers will not re-run if the associated KT_***_INITIALIZED variable is set to true.
KT_DRAWER_INITIALIZED = false;
KT_DROPDOWN_INITIALIZED = false;
KT_MENU_INITIALIZED = false;
KT_MODAL_INITIALIZED = false;
KT_REPARENT_INITIALIZED = false;
KT_SCROLL_INITIALIZED = false;
KT_TABS_INITIALIZED = false;
KT_TOOLTIP_INITIALIZED = false;

// Reinitialize all KTComponents
KTComponents.init();
});


Deleted comment

Ian, thanks, thanks a lot. You saved me a couple of weeks I have been fighting with this. Thanks for sharing.



Hi Ian Adams

That’s great progress! Honestly, it’s impressive that you’re diving into this on your own, especially with Rails 8 and Metronic 9 still being pretty new.

I had a look at your proof-of-concept, and it’s looking good! I’m sure your work will help others and might even give KeenThemes some ideas for our official guide.

Thanks for your feedback and progress! We appreciate you sharing your insights and guiding others along the way. happy



Hey Ian,

Thanks so much for this guide.

I have a quick question if you have some time.

How do you configure the fonts?

After following the steps here, I can see the ki-icons are not correctly rendered, and I wonder what I'm missing. Do you need to add something extra to render the icons property?

Thanks so much in advance!



Take a look at your styles.bundle.css file under app/assets/stylesheets.
You'll find four @font-face { } sections with URLs in them. In the file generated by Metronic HTML, those sections will contain lines that look something like this:

@font-face {
font-family: "keenicons-duotone";
src:
url("fonts/keenicons-duotone.ttf?gcn9yo") format("truetype"),
url("fonts/keenicons-duotone.woff?gcn9yo") format("woff"),
url("fonts/keenicons-duotone.svg?gcn9yo#keenicons-duotone") format("svg");
font-weight: normal;
font-style: normal;
font-display: block;
}


You need to get rid of the "fonts/" part, because when Propshaft evaluates those lines, the "fonts/" will mess Propshaft up. Remember, these instructions are for Rails 8, which uses Propeshaft by default. It may be different on 7.2, under the old asset pipeline.

Under Rails 8, you should modify those sections to look like this:

@font-face {
font-family: "keenicons-duotone";
src:
url("keenicons-duotone.ttf?gcn9yo") format("truetype"),
url("keenicons-duotone.woff?gcn9yo") format("woff"),
url("keenicons-duotone.svg?gcn9yo#keenicons-duotone") format("svg");
font-weight: normal;
font-style: normal;
font-display: block;
}


What's happening is Propshaft will look for your font files under app/assets/fonts. Any font files that it finds will be put into /assets when you launch the rails server, so on-the-fly, Propshaft is going to modify the styles.bundle.css file, and resolve where the browser should look for the font files.

Once your rails server is running, if you look in developer tools at the CSS files being passed to your browser, you'll see lines like this in the styles.bundle.css file, courtesy of Propshaft:

url("/assets/keenicons-duotone-91cab036.ttf?gcn9yo") format("truetype"),


When everything is working correctly, you just call the fonts as is done in the HTML templates provided by Metronic. For example:

<i class="ki-filled ki-users text-lg">



Update:

I was able to fix the mobile sidebar that was breaking after turbo loads.

So far, I haven't been able to cleanly fix the large-screen sidebar losing its collapsed state between turbo loads - but I don't actually want the sidebar to be collapsible, so I'm just removing the button that does that.

Since the demo1.js file just sets up that function and the MegaMenu, which isn't used in Demo1 anyway, I removed demo1.js from the application.js file.

Remove from home.html.erb and home2.html.erb:

<button class="btn btn-icon btn-icon-md size-[30px] rounded-lg border border-gray-200 dark:border-gray-300 bg-light text-gray-500 hover:text-gray-700 toggle absolute left-full top-2/4 -translate-x-2/4 -translate-y-2/4" data-toggle="body" data-toggle-class="sidebar-collapse" >
<i class="ki-filled ki-black-left-line toggle-active:rotate-180 transition-all duration-300">
</i>
</button>


Updated full application.js file:

// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails

import "@hotwired/turbo-rails"
import "controllers"
import "core.bundle"
import "apexcharts.min"


// Function to run the mainInitializers
function mainInitializers() {
KTComponents.init()
KTMenu.initHandlers()
KTDropdown.initHandlers();
KTTooltip.initHandlers();
}

mainInitializers()

document.addEventListener("turbo:load", function() {
mainInitializers()

// Reinisitialize the KTDrawer entitites after Turbo has loaded.
// Needed to fix sidebar on mobile.
KTDrawer.handleToggle();
KTDrawer.handleDismiss();
KTDrawer.handleResize();
KTDrawer.handleClickAway();
KTDrawer.handleKeyword();
});



Hi Ian,

Thank you for your incredible work on integrating Metronic 9 with Rails 8! Your detailed guide are fantastic resources that will definitely help other developers with similar challenges. We truly appreciate your efforts.

We’re considering adding official Rails support in the future, and your progress will be a valuable reference. Keep up the great work! happy



Almost forgot the Python script.

Run it with:
python convert_img_tag.py get-started.html
That will read in an HTML file, and rewrite it, with all the img tags converted. But you do need to open the file and search for any instances of closing img tags like </img>
There will be just a few of those you need to clean up by hand.

convert_img_tag.py

import re
import sys

def convert_to_rails_image_tag(img_tag):
# Regex to extract src, alt, and class attributes
regex = r"(class="[^"]*"|src="[^"]*"|alt="[^"]*")"

# Find all matches
matches = re.findall(regex, img_tag)

# Initialize attributes dictionary
attributes = {
"src": "",
"alt": "",
"class": ""
}

# Populate attributes dictionary
for match in matches:
if match.startswith("src="):
src_value = match.split("=")[1].strip(""")
# Strip off "/assets/" prefix
if src_value.startswith("assets/"):
src_value = src_value[len("assets/"):]
attributes["src"] = src_value
elif match.startswith("alt="):
attributes["alt"] = match.split("=")[1].strip(""")
elif match.startswith("class="):
attributes["class"] = match.split("=")[1].strip(""")

# Build the Rails image_tag
rails_image_tag = f"<%= image_tag "{attributes["src"]}""
if attributes["alt"]:
rails_image_tag += f", alt: "{attributes["alt"]}""
if attributes["class"]:
rails_image_tag += f", class: "{attributes["class"]}""
rails_image_tag += " %>"

return rails_image_tag

def convert_img_tags_in_file(file_path):
with open(file_path, "r") as file:
content = file.read()

# Regex to find all img tags
img_tag_regex = r"<img[^>]*>"
img_tags = re.findall(img_tag_regex, content)

for img_tag in img_tags:
rails_image_tag = convert_to_rails_image_tag(img_tag)
content = content.replace(img_tag, rails_image_tag)

with open(file_path, "w") as file:
file.write(content)

if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python convert_img_tag.py <file_path>")
sys.exit(1)

file_path = sys.argv[1]
convert_img_tags_in_file(file_path)


Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(
Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(