Dynamic SASS generation - ruby-on-rails

I am working on a multi tenant app where I'd like each Tenant to control the colors of specific elements. For example, I'd imaging have a form w/ color pickers where users could control items such as site background color, navbar colors, etc.
I have a baseline SASS(.scss) file which sets the default color scheme. My questions are:
How would I then load the "dynamic" theme .scss file?
If I had model fields like Tenant.nav_bar_link_color, how would I load those values into the SASS theme file?
Would I be able to/should I somehow precompile the Tenant specific themes into the asset pipeline?

You could add a class to the body element of the main application. This could be like
<body class="">
Then programmatically add a class such as "scheme-red" or "scheme-blue" to this body element based on their selection of a theme.
In your SASS you would then have different color schemes that override the defaults.

After some research, I think the method outline here makes most sense:
User generated custom css
Basically i'll store the Tenant controlled CSS values in the DB, and then render them out in the head as overrides against my default SASS file....

Related

Bootstrap 5.3 Contained Color Modes - Customize with no Sytle Leakage?

I am working to customize bootstrap 5.3 in a way that can be contained inside any element of a web page as a third party that will not interfere with outside styling including any other use of bootstrap outside my section of the page.
To do this I have changed data-bs-theme attribute to myprefix-data-bs-theme, via find/replace in compiled css.
When compiling scss, I import functions, override variables, import variables, then variables-dark, maps, mixins and then root as such:
// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
#import "../bootstrap/scss/functions";
// 2. Include any default variable overrides here
// 3. Include remainder of required Bootstrap stylesheets
#import "../bootstrap/scss/variables";
#import "../bootstrap/scss/variables-dark";
// 5. Include remainder of required parts
#import "../bootstrap/scss/maps";
#import "../bootstrap/scss/mixins";
#import "../bootstrap/scss/root";
When root is imported, the css compiles to:
:root, [data-bs-theme=light] { ... variables }
[data-bs-theme=dark] { ... dark variables }
This sets light variables and root and any element having attribute data-bs-theme=light and data-bs-theme=dark variables on any element that has data-bs-theme=dark. I originally thought I could remove root, but noticed that some variables like font family are set there, so if :root is removed, you must add another selector to pick those up for dark mode/theme. I chose to do this by wrapping my content in a div with a specific id an replacing :root with #my-id. This had to be done manually in compiled css.
As stated, I am prefixing data-bs-theme with a custom prefix. If I do that and remove the :root selector, while setting the myprefix-data-bs-theme attribute on the html element, then I should not be messing with any other potential bootstrap variables from a potential third party.
I then wrap the bootstrap import in a custom selector as so
.ml-custom-selector {
#import bootstrap
}
This of course causes some css semi-duplication as root has already been imported. I believe I was following what was documented in customizing bootstrap on the site, but what I am seeing, I think I need to do the below:
I appears I need to just pull out the imports in bootstrap.scss that are needed, so nothing is duplicated such as the root import (the :root variables appear twice in compiled css)
Remove :root from the :root and light mode selector where original variables are declared and replace it with a selector to the containing element of my code to pick up variables such as font family.
Add myprefix-data-bs-theme to elements that will use my customizations on the page as to not step on another bootstrap implementation
Use a custom selector and import the remaining parts of bootstrap
Concerns:
If I leave :root and another third party uses bootstrap, we will have variable collisions
:root is also used for smooth scrolling behavior and .my-custom-selector :root { scroll-behavior: smooth; } won't work. At least to my knowledge. This is also true for .my-custom-selector body { body override css } since I only want my custom selector on elements that are dedicated as my part of the page and can't override other parts of the page by setting anything on the body or changing box-sizing for the entire page.
Questions:
Is it okay to remove root where seen and just have the myprefix-data-be-theme attribute as selector there. I believe yes
Where body is in css, can I just change body to .ml-custom-selector and not interfere with any other parts of the web page. I also believe yes
Can I automate this process with sass or do I need to manually change css during sass compilation or will I have to write a tool to do this? The concern is that I want contractors to be able to easily create new themes/modes and not have to know about the extra steps that are involved. Just override variables, compile scss and viola
I am new to sass and very much appreciate your input. Many thanks!

How do I reference the brand color in inline (app specific) css on the index.html

I have a habit of putting custom css that is app specific in the inline head <style> tags of index.html. This way I can adapt SAP defined classes differently across apps.
Is there a way to reference the brand color (and any other defined color) from the UI Theme that is used from within the inline CSS?
Have you tried the css class "sapBrandColor" or "sapUshellShellBrand"? However, according to the docs you should use "sapThemeBrand-asColor".
Here is the right documentation:
CSS Classes for Theme Parameters
List of Supported CSS Classes

User theme switching with SASS - 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.

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