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
~/projects/html/metronic-v9.0.6/metronic-tailwind-html/tailwind.config.js
copy just these two sections:darkMode: "class",
theme: { ....bunch of stuff... }
~/projects/rails8/met3/config/tailwind.config.js
.config/importmap.rb
:pin "core.bundle", to: "core.bundle.js"
pin "apexcharts.min", to: "apexcharts.min.js"
pin "demo1", to: "demo1.js"
import "core.bundle"
import "apexcharts.min"
import "demo1"
app/assets/stylesheets/styles.bundle.css
file a little bit.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");
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");
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>
app/views/pages/home.html.erb
and app/views/pages/home2.html.erb
files.<!-- 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.metronic-tailwind-html/dist/html/demo1/public-profile/activity.html
to app/views/pages/home2.html.erb
.bin/dev
and you should see a Metronic page with broken images at HTTP://localhost:3000 .<img class="max-h-[25px] w-full" src="assets/media/app/mini-logo.svg"/>
<%= image_tag "media/app/mini-logo.svg", class: "max-h-[25px] w-full" %>
<img src="something.png">lots of tag</img>
<%= image_tag "something.png", class: "whatever" do %>lots of tags<% end %>
<%= link_to pages_home_path %>
<%= link_to pages_home2_path %>
document.addEventListener("turbo:load", function() {
KTComponents.init()
KTMenu.initHandlers()
KTDropdown.initHandlers();
KTTooltip.initHandlers();
});
KTComponents.init()
KTMenu.initHandlers()
KTDropdown.initHandlers();
KTTooltip.initHandlers();
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();
});
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.
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;
}
@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;
}
url("/assets/keenicons-duotone-91cab036.ttf?gcn9yo") format("truetype"),
<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>
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!
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)