User theme switching with SASS - Ruby on Rails - ruby-on-rails

So I have an rails admin system that will allow a user to choose a theme, basically a set of SASS color variables that will recompile application.css.scss with the new colors. How would be the best way of going about changing this when the user selects from a drop down and submits? I read some up on some problems with caching and recompiling but I'm not totally clear how to set it up.
Currently I have..
application.css.scss
#import "themes/whatever_theme";
#import "common";
#import "reset";
#import "base";
themes/_whatever_theme
$theme_sprite_path: '/images/sprite_theme_name.png';
$main_color:#009DDD;
$secondary_color:#b3d929;
$light_background:#f2f2f2;
$border_line:#e6e6e6;
$off_white:#f9f9f9;
$white:#ffffff;
$font_body:#565b59;
$font_headers:#363a36;
Say I have 5 different themes the user will switch between, it would be nice to set variable names for each theme in Rails then pass these down to SASS and change them on the fly and recompile. Is this the best way to go about this?

3 easy steps:
Compile all themes into different files upon deploy. This will take care of timestamping, zipping, etc.
Render page with default theme.
Use javascript to load alternate theme CSS.
No need to mess with dynamic compilation and all that.
To load a CSS dynamically you can use something like this:
function loadCSS(url) {
var cssfile = document.createElement("link");
cssfile.setAttribute("rel", "stylesheet");
cssfile.setAttribute("type", "text/css");
cssfile.setAttribute("href", url);
}

Sergio's answer is valid, but omits the sassy details and I'd used a slightly different approach.
You're using SASS in Rails- don't fight the current, be Railsy and let the asset pipeline precompile all your CSS. Unless you're trying to do something extreme like CSSZenGarden with hundreds of themes, or each theme is thousands of lines I'd recommend setting each theme as it's own CSS class rather than it's own file.
1kb of extra CSS in the rendered application.css file won't bog down your users
It's straightforward to switch theme classes with JQuery: $(".ThemedElement").removeClass([all your themes]).addClass("MyLittlePonyTheme");
As implied, you will have to tag the elements you want the update with the ThemedElement class
You could alternatively just change the class on your top level element and make liberal use of inheritance and the !important declaration, although I find the other approach more maintainable.
If you think you can manage your themes with classes rather than files, here's how we generate them with SASS. SASS doesn't support json style objects, so we have to reach way back and set up a bunch of parallel arrays with the theme properties. Then we iterate over each theme, substitute the dynamic properties into the auto generated theme class, and you're off to the races:
themes.css.scss
#import "global.css.scss";
/* iterate over each theme and create a CSS class with the theme's properties */
#for $i from 1 through 4{
/* here are the names and dynamic properties for each theme class */
$name: nth(("DefaultTheme",
"MyLittlePonyTheme",
"BaconTheme",
"MySpaceTheme"
), $i);
$image: nth(("/assets/themes/bg_1.png",
"/assets/themes/bg_2.png",
"/assets/themes/bg_3.png",
"/assets/themes/bg_4.png"
), $i);
$primary: nth((#7ca8cb,
#3c6911,
#d25d3a,
#c20d2c
), $i);
$font: nth((Rosario,
Helvetica,
Comic Sans,
WingDings
), $i);
/* Now we write our Theme CSS and substitute our properties when desired */
.#{$name}{
&.Picker{
background-image:url($image);
}
color: $primary;
.BigInput, h1{
color: $primary;
font-family: $font, sans-serif !important;
}
.Frame{
background-image:url($image);
}
.Blank:hover{
background-color:mix('#FFF', $primary, 90%) !important;
}
.BigButton{
background-color:$primary;
#include box-shadow(0,0,10px, $primary);
}
/* and so on... */
}
It's a bit of a hack, but it's served us really well. If your themes are uber complicated or you have too many of them it gets more painful to maintain.

One option is to simply load a set of custom css rules (your theme) after your application.css and let your theme override the default colors from application.css. You could just add a database column "theme" and load the css with this name dynamically like.
SASS is not designed for compiling dynamic data on the fly. If you want dynamic css processing, you could add a controller method called "custom_css" and make this respond to the css format and load this dynamically with inline variables, but I don't think SASS is meant to be used for it at all.

I believe that you could use erb to inline variables in sass. I'm not positive, but I think it would look something like this:
themes/_whatever_theme.sass.erb
$theme_sprite_path: '<%= Theme.sprite_path %>';
$main_color: <%= Theme.main_color %>;
$secondary_color: <%= Theme.secondary_color %>;
These should be created dynamically for each page load. I'm not sure how the caching would work here.

Related

Using Less with AngularDart components and Bootstrap

I have started to migrate an AngularJS app to AngularDart, using Vic Savkin's sample app as a skeleton.
My existing project uses Less CSS. I am unclear about how to include that in my project.
Specifically, I have Components in the 'lib' folder and CSS in the 'web' folder. I want to be able to define some Less variables at project level, which will be used by Components. I will want to use the Components in other future projects.
Do I need to import the bootstrap.less and variables.less file in each component's less file, or is there a more efficient way?
At first if you want to use something in web and lib put it in lib. You can import from lib to web with package:mypackage/asset/css/mycss.css but not the other way around.
I guess you will avoid shadowDOM because Bootstrap doesn't work well with it.
If you still want to use shadowDOM you need to add the CSS to each component (might still cause troubles when you have nested components) or modify the CSS selectors.
If you modify the CSS selectors you need to add additional selectors for each rule like:
/* original */
.someclass .someotherclass { xxx: yyy; }
/* modify to */
.someclass .someotherclass,
* /deep/ .someclass /deep/ .someotherclass,
* /deep/ .someclass .someotherclass, /* might be necessary too */
{ xxx: yyy; }
If you create all your components (and use only 3rd-party components that don't use shadowDOM) you can put all CSS into the index.html page like in Angular.js.

Maintain RTL version of stylesheets with rails asset pipeline

Background
I want to enable right-to-left locales as well as left-to-right, but I only want to maintain a single set of stylesheets.
The idea is that calling application-rtl.css will serve a rtl-converted version of application.css (using r2).
This functionality has two use-cases:
development: serve dynamically, converting on the fly
production: have precompilation generate the -rtl versions (extending rake assets:precompile task)
So far, I've managed to implement a RTLConverter that enables me to serve all my stylesheets converted to RTL without having touched them at all:
config/initializers/rtl_converter.rb:
require "r2"
require "tilt"
class RTLConverter < Tilt::Template
def prepare; end
def evaluate(context, locals, &block)
R2.r2 #data
end
end
Rails.application.assets.register_preprocessor 'text/css', RTLConverter
You can also implement this as an engine for sprockets to only convert files having the .rtl extension:
Rails.application.assets.register_engine 'rtl', RTLConverter
My question
How can I hook into the asset pipeline in order to:
serving an on-the-fly converted version of any stylesheet with the name-postfix '-rtl' (look for the file without the postfix and serve a converted version of that)?
creating converted copies with the name-postfix '-rtl' of all stylesheets during precompilation
Notes:
The converter does not work in conjunction with the sass engine, but seems to work fine with less. It's been applied to a twitter-bootstrap based site and works like a charm.
The converter has not been tested in production.
If I can find a decent solution to this problem, I intend to create and maintain a gem and give it back to the community.
I would aim for just using CSS directly to handle the LTR-RTL differences. CSS can probably handle the job for you. In case you define your CSS as LTR, then, based on a CSS class you can override the stuff you want.
Per default, define your entire CSS stylesheet as LTR.
Put an extra css class on the body which handle the locale. For instance <body class="RTL">
Define all exceptions to the default by overriding with CSS classes prefixed by "RTL"
A short example. Original "default" styles:
body { direction: ltr; }
.sidebar { width: 200px; float: right; margin-left: 30px }
Then, overwrite relevant styles later in your css:
body.rtl { direction: rtl; }
.rtl .sidebar { float: right; margin-right: 30px; }
/*remember to 'reset' all defaults for example: */
.rtl .sidebar { margin-left: 0; }
With sass, you have a common place to define standard variables, for instance margin in the example above. Your mileage may vary. But to me, that sounds more simple than messing with asset pipeline.
Unfortunately, none of the other answers really suggested the solution I was looking for, so after digging into matters, I was able to come up with a simple gem that could flip the stylesheet based on the filename.
So by including that gem and serving up a version of the stylesheet with '-flipped' appended to the name, I am now able to serve an automatically flipped version both in dev and production.
Find the gem on rubygems.org: stylesheet_flipper
Find a usage description on gihub: monibuds/stylesheet_flipper
As far as I can tell, Sprockets does not expose any hooks into the path lookup mechanism to processors. Even if it did, I don't think you could invoke some generalized "read asset" method.
All in all, I fear that making AP do what you want would require some serious arm-twisting.
So here's a completely different idea. What if you:
Dropped on-the-fly asset processing altogether, even in development
Had a Guard task set up to
recompile assets when their sources change (I think this exists already)
generate the RTL versions of the compiled CSS
Push the compiled assets to production (gasp!)
In other words, maybe you could trade some of the AP features for increased flexibility?
(I'm not going for the bounty here because this is just an idea, not a solution)
Wrote about this issue a year ago:
http://amitkazmirsky.com/2011/05/29/dry-your-rtl-and-ltr-css-files-in-rails-with-sass/
Basically my approach was to write the css with direction as variables:
#user-box {
backgroud-color: white;
padding: 0 5px;
float: $dir;
}
#side-nav {
margin-#{$opdir}: 5px;
}

Is there a way to extend the SASS preprocessor to manipulate arbitrarily each declared property?

I am developing an embeddable widget that needs to have all its CSS properties declared as important to prevent CSS bleed of the embedding page. This means that if I want to use some pre-existing CSS framework (like Bootstrap), or some jQuery plugin that uses a CSS stylesheet, I have to manually copy-paste the CSS in my assets folder and add !important declarations to each property. This seems a rather unmaintainable and error prone process.
As per title, is there a way to extend the SASS preprocessor to add !important to any declared property for an imported file or partial?
No,
Sass doesn't have that functionality, because it is the most uncommon thing you would want to do in Sass, or CSS, or anywhere for that matter.
However, from what I understand, you want to add in the !important to all the CSS properties in a particular file. In that case, you can just simply do a Search & Replace:
Search for ; and replace with !important;
The most obvious solution is to create a new mixin, potentially with the word important appended like so:
%margin-none-important {
margin: 0 !important;
}
And then in your code:
.no-margin {
#extend %margin-none-important;
}

How do I use CSS to style my form components?

How does Vaadin use CSS that was written purely for HTML elements (e.g. styling and layout of body, h1, etc elements) and use that exact CSS style in Vaadin?
Does one need to make changes on the CSS to map to corresponding Vaadin elements, or can one use that CSS as is?
You can use the CSS as is, but you'll (naturally) have to tell Vaadin which CSS classes to use by calling
myComponent.setStyleName("myStyleClass");
or
myComponent.addStyleName("myStyleClass");
The difference between the two is that setStyleName replaces all existing styles with the provided one and addStyleName doesn't replace anything but adds the provided style for the component.
You can also modify your CSS to override default Vaadin styles, for example
.v-panel .v-panel-content {
background: yellow;
}
would change every Panel's background color to yellow.
I recommend that you create a new theme which is based on an existing Vaadin theme. Here's how to:
Create a directory in the VAADIN/themes/ directory (eg. VAADIN/themes/myTheme).
Create styles.css in your theme directory.
Add #import "../runo/styles.css"; to the beginning of your styles.css (you can replace runo by any other existing theme).
Call setTheme(myTheme); in your Vaadin application class.
If you want to apply application-wide styles, override the Vaadin component CSS definitions in your styles.css. If you don't know the names of the CSS classes, you can use firebug and check the HTML elements' classes.
If you want to create a component-specific style, define it in styles.css and call setStyleName or addStyleName methods.
See the CSS entry in the Book of Vaadin here.
As far as I can tell from looking at the demos, Vaadin just generates HTML, so yes.
Does one need to make changes on the CSS to map to corresponding Vaadin elements, or can one use that CSS as is?
You can use your own CSS styles (just as it is) and it can use for either individual components (as said by "miq*" earlier) or entire page. `
Here is a link for more info:
https://vaadin.com/book/-/page/themes.css.html

How can I access Rails objects in Sass?

In a Rails 3.1.0 project, I have Companies with a few customizable attributes like background_color and link_color. I want to be able to set some Sass variables like so:
$background_color: <%= #company.background_color %>
$link_color: <%= #company.link_color
...
This doesn't work because #company is nil when Sass does its thing. I'm not sure how to go about solving this in a way that's dynamic (companies can be created and colors can be changed and the views update immediately). Any suggestions?
I can think of a couple approaches off the top of my head:
Serve your stylesheet through a controller.
Use CSS classes to configure the colors and serve just that CSS through a controller, inlined partial, or a CSS #import.
Serving your stylesheet through a controller is pretty straightforward so there's not much to say. This might be a bit ugly and cumbersome.
For the second one, you'd add a couple extra CSS classes:
.custom-bg {
background-color: some-default-bg;
}
.link-fg {
color: some-default-fg;
}
/*...*/
Then any element that need to use the custom background color would need their usual CSS classes and custom-bg; similar shenanigans would be needed for the other configurable values. To supply the customized CSS, you could inline a <style> element into your HTML using a standard ERB partial or you could serve the CSS through a controller (either through <style src="..."> or #import). So you'd fake the SASSy goodness with old school multiple CSS classes in your HTML.
There's also JavaScript. You'd need some way to identify the elements that need their colors adjusted and then adjust them directly with things like this:
$('.need-custom-background').css('background-color', '...');
I think you might be able to do something just like what you have there, but you need to change the extensions of the files to '.css.scss.erb'
To follow up on this, I did create a stylesheet controller but it was rather contrived to get Sass parsing and asset pipeline load paths all working correctly. I ended up dumping that and reorganizing the styles so I could generate a static stylesheet for each company which gets regenerated and uploaded to S3 on company update.
Well, if you mean a dynamic object like a model loaded via a controller, you can't really, at least not very easily. This is because unlike HTML ERB templates, the SASS ones are generally rendered once and served statically unless something changes in the code or they are re-precompiled via rake (depending on your environment configs). But you can access some helper methods, global objects, and use some ruby in there by renaming the file with an "erb" extension e.g. application.css.scss.erb. See
https://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets
How can I use Ruby/Rails variables inside Sass?)
If you need styles based on dynamically loaded objects, like models, you can...
Write CSS styles literally in the template
Compile the stylesheets dynamically. See the top-rated answer here: How do I create dynamic CSS in Rails?
For some use cases you might accomplish the same thing by leveraging Rails/SASS's import path hierarchy (i.e. SASS #import 'partial_name_with_no_path' will search the importing SASS files folder first and then fall back to the top level - You can configure this as well).

Resources