How can I customize the active admin layout? - ruby-on-rails

I need to customize the active admin layout, but how can I do it?

The active admin layout is not actually defined as a layout file, but is generated programatically. Placing a custom layout in the layout directory will therefore not actually override the default layout.
You can, however, monkey-patch or duck-punch the active admin layout methods inside your application.
The following will add an ie-specific stylesheet to the header:
module ActiveAdmin
module Views
module Pages
class Base < Arbre::HTML::Document
alias_method :original_build_active_admin_head, :build_active_admin_head unless method_defined?(:original_build_active_admin_head)
def build_active_admin_head
within #head do
meta :"http-equiv" => "Content-type", :content => "text/html; charset=utf-8"
insert_tag Arbre::HTML::Title, [title, active_admin_application.site_title].join(" | ")
active_admin_application.stylesheets.each do |path|
link :href => stylesheet_path(path), :media => "screen", :rel => "stylesheet", :type => "text/css"
end
active_admin_application.javascripts.each do |path|
script :src => javascript_path(path), :type => "text/javascript"
end
text_node csrf_meta_tag
text_node "<!--[if lt IE 7]>
<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"admin_ie7.css\ />
<![endif] -->".html_safe
end
end
end
end
end
end
Clearly an ugly solution.

When a view is defined in a gem AND in the rails app, the one defined in the Rails app is served. It's a logic priority.
So if you need to override all or some active admin views, you'll have to copy these in your app and change them as you desire.

Maybe ActiveAdmin does provide a nicer way to do this by now? I don't know.
However here would be an example for a bit cleaner patch for that situation, in my example to add the webpacker gems javascript_pack_tag to my admin area.
module MyApp
module ActiveAdmin
module Views
module Pages
module BaseExtension
def build_active_admin_head
super
within #head do
text_node(javascript_pack_tag('application'))
end
end
end
end
end
end
end
class ActiveAdmin::Views::Pages::Base < Arbre::HTML::Document
prepend MyApp::ActiveAdmin::Views::Pages::BaseExtension
end

(Using rails 5.1.4) I tried two solutions here that involved messing with the active_admin library, and they did not work for me at all. I found my solution in config/initializers/active_admin.rb. I am adding a small amount of bootstrap styling to the default layout. As far as linking to stylesheets, javascripts, etc., it was as simple as adding this to my active_admin.rb, as per the comments therein:
# == Register Stylesheets & Javascripts
#
# We recommend using the built in Active Admin layout and loading
# up your own stylesheets / javascripts to customize the look
# and feel.
#
# To load a stylesheet:
# config.register_stylesheet 'my_stylesheet.css'
config.register_stylesheet 'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css', { integrity: 'sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk', crossorigin: 'anonymous' }
As far as editing that generated layout, I have yet to figure it out, but it could at least be done indirectly via JavaScript and the inclusion of that javascipt in this file via
config.register_javascript 'active_admin_view_tweaks.js', { defer: true }
I am going to be editing class attributes to make my pages responsive with bootstrap, so I might follow something like this geeksforgeeks article to edit the pages with JavaScript after they've loaded.
I don't know if there is a way to edit the generated layout more directly, but this should work for some cases.

You can override the active admin page layout by putting the following code in your config/intializers/active_admin.rb file:
module AdminPageLayoutOverride
def build_page(*args)
within super do
render "shared/your_custom_view_partial"
end
end
end
ActiveAdmin::Views::Pages::Base.send :prepend, AdminPageLayoutOverride
In the above example, I have a custom view file at app/views/shared/_your_custom_view_partial.html.erb location and I am injecting that in all of my active admin pages by the above code.

Related

Live assets compilation only for one file in Rails

There is config.assets.compile=true parameter which enables so called "live compilation".
I need Rails to compile one specific asset (sample.css.scss.erb) on each request in live mode but the rest of assets should be still precompiled.
How can I achieve this?
Option 1 - put sample.css.scss.erb in a path different from config.assets.manifest (default="public/assets") and add it with javascript_include_tag
Option 2 - Remove it from the config.assets.precompile
config.assets.precompile -= %w( sample.css.scss.erb )
Make sure you clean then precompile to test it.
I did not test either option, please let us know if any works for you.
Live compilation for file is required when css is preprocessed by .css.erb file.
It means dynamically compiling css.
For example : allowing users to set the colors and would like to conditionally use "live compilation"
In that case i will suggest dynamically rendering /users/:id/styles.css.erb instead of live compilation.
Reason there might be millions of users and in production environment using Ec2 instance will cost you more. That's why i will suggest below solution:
First define your custom action on the UsersController:
# config/routes.rb
match '/users/:id/styles' => 'users#styles', :as => :user_styles
Link to the "stylesheet" in your layout:
# app/views/layouts/application.html.erb
= stylesheet_link_tag 'application', user_styles_path(current_user, :format => 'css')
Define the action in your controller. You could do whatever you want to retrieve the user's preferences here:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def styles
#colors = User.find(params[:id]).colors
end
end
That action will automatically render this view:
# app/views/users/styles.css.scss.erb
$background-color: <%= #colors[:background] %>;
body {
background-color: $background-color;
}
More ideas and suggetions are most welcome.

Rails static html template files in the asset pipeline and caching in development mode

I'm building a website using AngularJS and Rails. The HTML files that I'm using for templates are stored under /app/assets/templates and each time I update a route or change something inside of a nested partial inside of a template I need to "touch" the highest level file in the /app/assets/templates directory for the html file I'm changing.
So if I have a page "edit.html" which loads a partial "_form.html" then whenever I update a route or change something in _form.html I need to make sure that edit.html is touched.
This is annoying and very finicky. Is there any way to inform the asset pipeline/sprockets to avoid caching for the app/assets/templates directory?
The best solution I've found to this is not to use the asset pipeline for HTML template files.
Instead make a controller called TemplatesController and create only one action.
Then map all template URLs to that using a route such as:
get /templates/:path.html => 'templates#page', :constraints => { :path => /.+/ }
Then move all the template files into app/views/templates
Then inside the controller, setup the following:
caches_page :page
def page
#path = params[:path]
render :template => 'templates/' + #path, :layout => nil
end
This way all of your template files will be served from the controller and then will be cached into public/templates. To avoid cache problems, you can create a timestamp path into the template route so that your cached files are delivered with a version:
get '/templates/:timestamp/:path.html' => 'templates#page', :constraints => { :path => /.+/ }
This way you can have a new timestamp each time you upload the website and you can store the templates folder anywhere you like. You can even store the templates folder on S3 and have an assets URL for that. Then wherever your template files are addressed, you can use a custom asset method:
templateUrl : <%= custom_asset_template_url('some/file.html') %>
Where:
def custom_asset_template_url(path)
"http://custom-asset-server.website.com/templates/#{$some_global_timestamp}/#{path}"
end
Then just make the asset redirect to the Rails server if it's not found and it will be generated. Or all template files can be pre-generated once uploaded.
There's a much (much!) better way to deal with this.
<%= path_to_asset("template_name.html") %>
That will return a fully working file from the asset pipeline, which can use ERB, etc. It's undocumented, but it's a part of sprockets / the asset pipeline.
To my mind, several things are needed here:
The templates should be namespaced, so people templates go in the app/views/people/templates directory
The templates are entirely static, so no before filters should be called.
The templates should be cached, making them very fast.
Here's a possible solution using a Rails concern:
# Allows static content to be served from the templates
# directory of a controller
module HasTemplates
extend ActiveSupport::Concern
included do
# Prepend the filter
prepend_before_filter :template_filter, only: [:templates]
# Let's cache the action
caches_action :templates, :cache_path => Proc.new {|c| c.request.url }
end
# required to prevent route from baulking
def templates;end
# Catch all template requests and handle before any filters
def template_filter
render "/#{params[:controller]}/templates/#{params[:template]}", layout: 'blank'
rescue ActionView::MissingTemplate
not_found layout: 'blank'
false
end
end
Notice we are returning the template in a prepended filter. This allows us to return the static content without hitting any other filters.
You can then create a route, something like this:
resources :people do
collection do
get 'templates/:template' => 'people#templates', as: :templates
end
end
Your controller becomes simply:
class PeopleController < ApplicationController
include HasTemplates
end
Now any file placed in the /app/views/people/templates can be served at speed from a url.
Expanding on RandallB's answer a bit; this is mentioned explicitly in the documentation on the Asset Pipeline: http://guides.rubyonrails.org/asset_pipeline.html
Note that you have to append the extension .erb to your .coffee file to have this work. (e.g., application.js.coffee.erb)
You can try gem js_assets (https://github.com/kavkaz/js_assets).
This allows you to function asset_path in javascript code that emulates a similar method of sprockets.
Cleanest solution is to precompile your html assets using ejs and serve them as other javascript files.
No http query
minification
Possibility to pass js data to you templates to make them dynamic (ejs is a port from underscore template and basically
behave like erb for javascript)
It basically work like that :
#in you gemfile
gem 'ejs'
#in app/assets/javascripts/templates/my_template.jst.ejs
<p>my name is <%= name %> !<p>
#in your application.coffee
#= require_tree ./templates
JST['templates/my_template'](name: 'itkin')
=> '<p>my name is itkin !<p>'

conditional loading of stylesheet

I have a site with clients. Every client can have it's own theme and when a user of a certain client is logged in, the company theme must be loaded. In the application.css.scss I have a line like this for every company:
#import "_theme_x.css.scss";
#import "_theme_y.css.scss";
#import "_theme_z.css.scss";
How can I load only e.g. theme_x when a user of company x is logged in and not load theme_y and theme_z? Or is there a better way of doing this? Thanks!
If themes are large, you might want to separate them from the application.css and load them conditionally in your layout. For example, if you have a helper theme_stylesheet in application_helper which returns the name of the theme the client is using:
# application.html.erb
<%= stylesheet_link_tag 'application', theme_stylesheet %>
If they are small, I like namespacing. Leave your application.css as-is, but modify the themes to use a top-level rule on the body. Place a tag on the body to select the theme. The beauty of this is you can dynamically change the theme.
<body class="theme-<%= theme_stylesheet %>">
...
</body>
_theme_x.css.scss
body.theme-x {
...
}
you can do it in this manner like you can check first whose client is login and apply some
layout for him and include diffrent css files for diffrent layout.
Like i do
first i make method in application helper but i can implement layout according user roles you
can do it according clients.
def choose_layout
if is_admin?(current_user) or is_super_admin?(current_user)
'admin'
else
'application'
end
And in controller call it in before filter than layout will be implement according to user
roles
class AdministratorController < ApplicationController
include ApplicationHelper
layout :choose_layout
def index
#user = User.new
#current_user = current_user
end
end
hope you could get the idea.....

Using Rails' stylesheet_link_tag and javascript_include_tag in a Liquid Drop

I want to be able to give the users full control and edit the layout. I also want them to be able to include what javascript plugins they want. Therefore, I had to make an interface to allow them to do that.
For example, the default html looks like a more complicated version of this:
<head>
<title>{{site.name}}</title>
...
{{js_plugins.colorbox}} # this should return the necessary javascript and/or stylesheet tags
</head>
My Liquid JsPlugins drop is like this:
class JsPluginsDrop < Liquid::Drop
include ActionView::Helpers::AssetTagHelper
...
def colorbox
javascript_include_tag "/path/to/js"
end
end
When I run my specs though, I get this error (note that you see #drop["colorbox-1.3.15"] when the code I supplied above acts differently. However, I wanted to simplify my code since that's not the problem, it's the usage of the TagHelper that is the problem):
Failures:
1) JsPluginsDrop colorbox-1.3.15 should return the correct script tags
Failure/Error: #drop["colorbox-1.3.15"].stylesheets.should include("/jquery-plugins/colorbox-1.3.15/example1/colorbox.css")
undefined local variable or method `config' for #<JsPluginsDrop:0xcbfab38>
# ./app/drops/js_plugins_drop.rb:22:in `stylesheets'
# ./spec/models/js_plugins_drop_spec.rb:11
I won't be surprised if the problem is caused by the fact that this is separate from my Rails environment, and the drop does not have access to the config of Rails. Since I still want to be able to use these convenience methods and :cache => true that they give, how can I use the stylesheet_link_tag and javascript_include_tag from within a drop, if it's possible at all?
It seems that this is possible now when done this way:
class MyDrop < Liquid::Drop
...
def my_js_tag
helpers.javascript_include_tag '/some/thing'
end
...
def helpers
#helpers ||= ActionController::Base.helpers
end
end

Best way to handle dynamic css in a rails app

I'm researching a problem for handling dynamic css in a rails app. Within the app, individual users and/or groups of users can have customized look and feel that is accomplished via CSS. There will not be any fixed number of "look and feels" or css files, the number will grow as the number of users and groups grows and the look and feel is defined by the users via the application's admin interface. Throughout the course of a typical day thousands (it not tens of thousands) of different variations of the css will be served up. The app will store the pre-built css in mongodb, so there it will not have to pay the price of constructing the css for every request, the question is more about how is the best way to serve up this dynamic css content. I've seen other questions like [this one][1] that speak to using erb or sass, but some of these answers are dated by several years so I wanted to make sure there wasn't a better answer with Rails 3.
You can treat your CSS files as resources, store them on the database, and serve them with page caching, so that you only need to hit the db once when the CSS is modified. All later requests will be served directly by the web server from the cache, without ever touching your app or db.
# stylesheet.rb
class Stylesheet < ActiveRecord::Base
validates_presence_of :contents
end
# stylesheets_controller.rb
class StylesheetsController < ApplicationController
caches_page :show # magic happens here
def show
#stylesheet = Stylesheet.find(params[:id])
respond_to do |format|
format.html # regular ERB template
format.css { render :text => #stylesheet.contents, :content_type => "text/css" }
end
end
# the rest is your typical RESTful controller,
# just remember to expire the cache when the stylesheet changes
end
# routes.rb
resources :stylesheets
# layouts/application.html.erb
…
<link href="<%= stylesheet_path(#current_user.stylesheet) %>" rel="stylesheet" type="text/css" />
Well, I have worked with this a couple of times but they were definitely fixed no of CSS files to choose from. Its should be the same more or less.
One of things I used alot was the content_for blocks. Basically
<% content_for :css do %>
// some css file or css content
<% end %>
And in the layout
<%= yield :css %>
very simple way for managing the layouts.
This might give you a few ideas: Multiple robots.txt for subdomains in rails
I had a similar problem - but needed to serve the modified CSS only once.
I store a couple of constants in a module 'Site' - which I can then use as constants in CSS or as constants throughout the Rails application. I auto-generate the CSS files whenever the Rails application restarts and the CSS input files were modified.
You could do something similar, but reference symbolic names in site_settings.rb
and then fetch those on a per-user basis from MongoDB
http://unixgods.org/~tilo/Ruby/Using_Variables_in_CSS_Files_with_Ruby_on_Rails.html
Now let's say you have some dynamic styling called dynamic.css.scss.erb (the .erb at the end is important!) in app/assets/stylesheets. It will be processed by erb (and then by Sass), and as such can contain stuff like
.some_container {
<% favorite_tags do |tag, color| %>
.tag.<%= tag %=> {
background-color: #<%= color %>;
}
<% end %>
}

Resources