How do I make a list of CSS rules in LESS based on an unknown number of input arguments? - less

I'd like to make a LESS mixin for translating images like so:
.translate('/images/image.png', de, en-uk);
with an output that looks like this:
background-image: url('/images/image.png');
&:lang(de){ background-image: url('/images/image_de.png') }
&:lang(en-uk){ background-image: url('/images/image_en-uk.png') }
This would be easy if we were always translating the same number of languages, but unfortunately we are not (the content is the same across certain locales). I'm not sure how to make this number variable (which would future-proof the solution).
I guess what I'm looking for is a way to loop over each element in an array I pass and return another LESS rule for each.
Any ideas?

See Variadic mixin arguments, Loops, List Functions. For example it could be implemented somewhat like:
.test {
.translate('/images/image.png', grc, lat, san);
}
.translate(#image, #langs...) {
background-image: #image;
.-(length(#langs));
.-(#i) when (#i > 0) {
.-((#i - 1));
#lang: extract(#langs, #i);
&:lang(#{lang}) {
background-image: replace(#image, "\.", "_#{lang}.");
}
}
}
replace function requires Less 1.7.0 but for earlier versions you can use plain string interpolation/concatenation or format function as in #helderdarocha answer.
(Also note that the #langs... mixin parameter above can also accept the language list as a single variable), e.g.:
#languages: de, fr, es, ru, en-uk; // in fact commas here are optional too
.test {
.translate('/images/image.png', #languages);
}
And just in case, the same mixin using for wrapper (just to show that Less loops don't have to be that scary :):
#import "for";
.translate(#image, #langs...) {
background-image: #image;
.for(#langs); .-each(#lang) {
&:lang(#{lang}) {
background-image: replace(#image, "\.", "_#{lang}.");
}
}
}

This mixin uses target languages from a variable. It will loop through them and generate the code you want for each one:
.image-replace(#languages; #image-prefix) {
#count: length(#languages);
.loop(#count; #image-prefix);
.loop(#count; #image-prefix) when (#count > 0) {
.loop(#count - 1; #image-prefix);
#lang: extract(#languages, #count);
#image: %('%a_%a.png', #image-prefix, #lang);
&:lang(#{lang}){
background-image: url(#image);
}
}
}
To use it:
#languages: ~'de', ~'fr', ~'es', ~'ru', ~'en-UK', ~'pt-BR';
.section {
.image-replace(#languages; ~'/images/image');
}
Result:
.section:lang(de) {
background-image: url('/images/image_de.png');
}
.section:lang(fr) {
background-image: url('/images/image_fr.png');
}
.section:lang(es) {
background-image: url('/images/image_es.png');
}
.section:lang(ru) {
background-image: url('/images/image_ru.png');
}
.section:lang(en-UK) {
background-image: url('/images/image_en-UK.png');
}

Related

LESS: how to convert a list of called mixins to a for loop with a unique call

I crated many mixins to generate different kinds of classes for various purposes. Specifically I have to use a unique colorizer set using the standard bootstrap variable name, such as (only an example):
#type-primary: #fff;
#type-success: #f00;
#type-info: #ff0;
#type-default: #000;
#type-warning: #0f0;
#type-danger: #0ff;
Actually I created my mixins in the following form, with a "mother" as prefix to which I attached various suffixes
.text
{
&-primary { .color_text(#type-primary); }
&-success { .color_text(#type-success); }
&-info { .color_text(#type-info); }
&-default { .color_text(#type-default); }
&-warning { .color_text(#type-warning); }
&-danger { .color_text(#type-danger); }
}
After this, I can then create the final called mixin such as (so simple because it's only an example)
.color_text (#color)
{
color:#color;
}
I woud like to automate and optimize .text mixin to avoid many repeated rows, I think with a for loop. How could be possible?
Final results should be (in this case)
.text-primary {
color: #fff;
}
.text-success {
color: #f00;
}
.text-info {
color: #ff0;
}
.text-default {
color: #000;
}
.text-warning {
color: #0f0;
}
.text-danger {
color: #0ff;
}
In PSEUDO-CODE something like this could be ideal
#type-primary: #fff;
#type-success: #f00;
#type-info: #ff0;
.createContextClass("classNamePrefix",{#type-primary,#type-success,#type-info},mixinToBeCalled);
// Another call could be
.createContextClass("otherClassNamePrefix",{#type-primary,#type-success},otherMixinToBeCalled);
where, in relation to my original code, classNamePrefix should be the name of first part of final CSS class, then is passed an array with all kind of suffix that I wish in final CSS code, and mixinToBeCalled is the mixin that creates all css rules for final .text-primary, .text-success, .text-info.
For the moment, following Seven-Phases-Max' suggestion, I improved his solution in the following way

Overriding mixins in LESS

when defining a mixin multiple times in LESS, and later calling that mixin as follows
.background-color() {
background: red;
}
.background-color() {
background: yellow;
}
body {
.background-color;
}
the result will be a combined output from all the defined mixins
body {
background: red; // << output from mixin #1
background: yellow; // << output from mixin #2
}
while when you apply the same scenario in both Sass & Stylus ( using their own syntax of course ), when you call a mixin that is defined multiple times across your stylesheets, Only the last defined one will be executed ( it will override all previously defined mixins ) as follows.
result Sass and Stylus
body {
background: yellow; // << output from mixin #2
}
how can I override a mixin in LESS so that the output will be from the last defined mixin ?
You can not override them, alternatively use a variable to define the 'background-color'. For Less variables the last declared win.
Also read Pattern-matching
In Less all matching mixins are compiled in the source. You can use namespace to prevent name collisions, for instance:
#ns1 {
.background-color() {
background: red;
}
}
#ns2 {
.background-color() {
background: yellow;
}
}
than you can use:
body {
#ns2 > .background-color;
}
Double properties are also not removed to make some browser hacks possible, example:
#myElement {
width: 300px;
width: 500px\9;
}
To find a solution for your use case you should reformulate your question and explain why you have these same named mixins in the first place.

Loop over an array of name value pairs in LESS

Is there some way to loop an array of name/value pairs LESS? Something like this:
arr = alice: black, bob: orange;
.for(arr) // something something //
.cl-#{name} {
background-color: #{value}
}
To generate something like this:
.cl-alice { background-color: black; }
.cl-bob { background-color: orange; }
I know that you can for-loop an array, but I'm unsure if it can be an array of objects rather than values in LESS.
The answer given by #seven-phases-max works very well. For completeness you should also notice that you can do the same in Less without the imported "for" snippet.
from lesscss.org
In trying to stay as close as possible to the declarative nature of
CSS, Less has opted to implement conditional execution via guarded
mixins instead of if/else statements, in the vein of #media query
feature specifications.
and
In Less a mixin can call itself. Such recursive mixins, when combined
with Guard Expressions and Pattern Matching, can be used to create
various iterative/loop structures.
So in Less you could write:
#array: alice black, bob orange;
.createcolorclasses(#iterator:1) when(#iterator <= length(#array)) {
#name: extract(extract(#array, #iterator),1);
.cl-#{name} {
background-color: extract(extract(#array, #iterator),2);
}
.createcolorclasses(#iterator + 1);
}
.createcolorclasses();
or indeed:
#array: alice black, bob orange;
.createcolorclasses(#iterator:1) when(#iterator <= length(#array)) {
#name: extract(extract(#array, #iterator),1);
&#{name} {
background-color: extract(extract(#array, #iterator),2);
}
.createcolorclasses(#iterator + 1);
}
.cl-{
.createcolorclasses();
}
In Less a "pair" (in its simplest form) can be represented as an array too, so it can be as simple as:
#import "for";
#array: alice black, bob orange;
.for(#array); .-each(#value) {
#name: extract(#value, 1);
#color: extract(#value, 2);
.cl-#{name} {
background-color: #color;
}
}
Note however that the ".for" thing is limited to the only loop per scope so it's better to rewrite above to something like this:
#import "for";
#array: alice black, bob orange;
.cl- {
.for(#array); .-each(#value) {
#name: extract(#value, 1);
&#{name} {
background-color: extract(#value, 2);
}
}
}
The imported "for" snippet (it's just a wrapper mixin for recursive Less loops) can be found here (with examples here and here).
While it is useful from the other answers to know that Less supports recursive functions and mixins, there is now a much simpler answer to this simple question. This solution is tested to work with Less v3.9, but should work back to Less v3.7 when each was introduced.
.array() {
alice: black;
bob: orange;
}
each(.array(), {
.cl-#{key} {
background-color: #value;
}
});
The output is tidy:
.cl-alice {
background-color: black;
}
.cl-bob {
background-color: orange;
}
Want more? Well, as they say, "You can haz more". Use #index to use the 1-based index in the formula above.
Here is one "parametric mixins" which you can use with "key:value" pairs.
Array of "key:value" pairs is defined as follows: #array: "key:value", "key:value";
// imported "for" snippet (it's just a wrapper mixin for recursive Less loops)
// http://is.gd/T8YTOR
#import "for";
// loop all items and generate CSS
.generate_all(#array) {
.for(#array);
.-each(#item) {
#name: e(replace(#item, ':(.*)', ''));
#value: replace(#item, '^[^:]*:', '');
#{name} {
z-index: e(#value);
}
}
}
Definition:
#array_test: ".test:9000", "header .mainNav:9000", "footer:8000", "li.myclass:5000";
Test
.generate_all(#array);
Result:
.test {
z-index: 9000;
}
header .mainNav {
z-index: 8000;
}
footer {
z-index: 7000;
}
li.myclass {
z-index: 5000;
}
It works for me using grunt + less#1.7.4

LESS CSS Variable based on variable

I'm using LESS to build a site, and want the layout to switch based on direction, either 'ltr' or 'rtl' (direction:rtl)
I set #direction at the top. Then there are certain elements that I want floated left for ltr, and right for rtl. I also want to position absolute, and apply padding-left/right based on the #direction.
Instead of writing out separate mixins for float, pos and padding, I was trying to do something like this:
.mixin (#direction) when (#direction = ltr) {
#lr:left;
}
.mixin (#direction) when (#direction = rtl) {
#lr:right;
}
Then call it like this:
ol li {
float:#lr;
}
and/or
ol li span.date {
position:absolute;
#lr:0;
}
That's the idea, but any help would be appreciated. I've looked at guards, and parametric mixins but just can't seem to nail it.
I have an idea for your case which solve RTL problem. In each class, we define 2 mixin within it for direction stylesheet properties. The prototype like that:
// #direction variable should be put by somehow
#direction: rlt;
.foo {
// common properties;
.dir(#direction);
.dir(rtl) {
// RTL properties;
}
.dir(ltr) {
// LTR properties;
}
}
For example:
// #direction variable should be put by somehow
#direction: rlt;
.foo {
color: #000000;
.dir(#direction);
.dir(rtl) {
float: left;
padding-left: 5px;
background: "rtl.png";
}
.dir(ltr) {
float: right;
padding-right: 5px;
background: "ltr.png";
}
}
OK. After some playing and a bit of thinking this is what I've come up with. If I can't use variables as properties then I'll use #direction, and #directionOpp (opposite of rtl, ltr) to use as a layout helper.
I have 2 variables.
#direction: ltr; // Change to 'rtl' for arabic, hebrew, etc.
#directionOpp: rtl; // Make this opposite of #direction, for easier mixins
Here's my mixin for horizontal positioning.
#dir {
.dir(ltr,#dist:0) {left: #dist;}
.dir(rtl,#dist:0) {right: #dist;}
.float(ltr){float:left; }
.float(rtl){float:right;}
.margin(ltr, #dist:#a){margin-left:#dist;}
.margin(rtl, #dist:#a){margin-right:#dist;}
.padding(ltr, #dist:#a){padding-left:#dist;}
.padding(rtl, #dist:#a){padding-right:#dist;}
}
and here's how I call it.
ol li {
#dir.float(#direction);
#dir.padding(#direction);
}
If I ever need to reverse anything, then I can replace #direction with #directionOpp.
I can also specifiy how much #dist I need as it's parametric mixin, and since they're all separate I can have any combination of margin, float, padding etc I need without multiple mixins with hard coded properties.
Good solution?
Dave
Your first issue is that variables are actually constants in LESS. So once you set it, it can not be overwritten. When you set #lr to "left", then it will always have "left" as its value, even if you try to reset the variable. Which is the issue with your first idea.
As far as your second idea, LESS does not support using variables as properties, only values. However you can hack around it like so:
.mixin(#prop, #value) {
Ignore: ~"a;#{prop}:#{value}";
}
This isn't very clean, but it does the trick if you absolutely need the functionality. Word is it is in the works for 1.4.
Also, you are calling the mixin incorrectly. Try something like this:
.mixin (#direction) when (#direction = ltr) {
float: left;
}
.mixin (#direction) when (#direction = rtl) {
float: right;
}
Then call it like this:
ol li {
.mixin(ltr);
}
Which should spit this out:
ol li {
float: left;
}
Try this Mixins in your LESS files
#rtl: rtl;
#ltr: ltr;
#direction: #rtl;
then Use them like this
html {
direction: #direction;
}
body{
direction: #direction;
}
for Left and Right commands you should use these mixins
.DockItem(#location, #value) when (#location = "left") and (#direction = #ltr){
left: #value;
}
.DockItem(#location, #value) when (#location = "right") and (#direction = #ltr){
right: #value;
}
.DockItem(#location, #value) when (#location = "left") and (#direction = #rtl){
right: #value;
}
.DockItem(#location, #value) when (#location = "right") and (#direction = #rtl){
left: #value;
}
then in your less file you should call this mixins like this
.TestClass{
.DockItem('left', '100%');
}
Note that I set #direction to RTL so above style would result like this
.TestClass{
right: 100%;
}
If you set chnage the directionality of the page to LTR it would result like this
.TestClass{
left: 100%;
}
Let me know if it helps you or not
I implemented a rtl extension in dotless. Find it on github. That extension reverwses float:left to float:right and margin-left:5px to margin-right:5px.
It also supports prefixing properties to control how they are reversed.
A lot more info is available on the dotless wiki for the plugin.
You can find generic information on how to use dotless and plugins also on the wiki.

Creating or referencing variables dynamically in Sass

I'm trying to use string interpolation on my variable to reference another variable:
// Set up variable and mixin
$foo-baz: 20px;
#mixin do-this($bar) {
width: $foo-#{$bar};
}
// Use mixin by passing 'baz' string as a param for use $foo-baz variable in the mixin
#include do-this('baz');
But when I do this, I get the following error:
Undefined variable: "$foo-".
Does Sass support PHP-style variable variables?
This is actually possible to do using SASS maps instead of variables. Here is a quick example:
Referencing dynamically:
$colors: (
blue: #007dc6,
blue-hover: #3da1e0
);
#mixin colorSet($colorName) {
color: map-get($colors, $colorName);
&:hover {
color: map-get($colors, #{$colorName}-hover);
}
}
a {
#include colorSet(blue);
}
Outputs as:
a { color:#007dc6 }
a:hover { color:#3da1e0 }
Creating dynamically:
#function addColorSet($colorName, $colorValue, $colorHoverValue: null) {
$colorHoverValue: if($colorHoverValue == null, darken( $colorValue, 10% ), $colorHoverValue);
$colors: map-merge($colors, (
$colorName: $colorValue,
#{$colorName}-hover: $colorHoverValue
));
#return $colors;
}
#each $color in blue, red {
#if not map-has-key($colors, $color) {
$colors: addColorSet($color, $color);
}
a {
&.#{$color} { #include colorSet($color); }
}
}
Outputs as:
a.blue { color: #007dc6; }
a.blue:hover { color: #3da1e0; }
a.red { color: red; }
a.red:hover { color: #cc0000; }
Sass does not allow variables to be created or accessed dynamically. However, you can use lists for similar behavior.
scss:
$list: 20px 30px 40px;
#mixin get-from-list($index) {
width: nth($list, $index);
}
$item-number: 2;
#smth {
#include get-from-list($item-number);
}
css generated:
#smth {
width: 30px;
}
http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#lists
http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#list-functions
Anytime I need to use a conditional value, I lean on functions. Here's a simple example.
$foo: 2em;
$bar: 1.5em;
#function foo-or-bar($value) {
#if $value == "foo" {
#return $foo;
}
#else {
#return $bar;
}
}
#mixin do-this($thing) {
width: foo-or-bar($thing);
}
Here's another option if you're working with rails, and possibly under other circumstances.
If you add .erb to the end of the file extension, Rails will process erb on the file before sending it to the SASS interpreter. This gives you a can chance to do what you want in Ruby.
For example: (File: foo.css.scss.erb)
// Set up variable and mixin
$foo-baz: 20px; // variable
<%
def do_this(bar)
"width: $foo-#{bar};"
end
%>
#target {
<%= do_this('baz') %>
}
Results in the following scss:
// Set up variable and mixin
$foo-baz: 20px; // variable
#target {
width: $foo-baz;
}
Which, of coarse, results in the following css:
#target {
width: 20px;
}
I came across the need to reference a colour dynamically recently.
I have a _colours.scss file for every project, where I define all my colours once and reference them as variables throughout.
In my _forms.scss file I wanted to setup button styles for each colour available. Usually a tedious task. This helped me to avoid having to write the same code for each different colour.
The only downside is that you have to list each colour name and value prior to writing the actual css.
// $red, $blue - variables defined in _colours.scss
$colours:
'red' $red,
'blue' $blue;
#each $name, $colour in $colours {
.button.has-#{$name}-background-color:hover {
background-color: lighten($colour, 15%);
}
}
I needed to use dynamic color values in sass variables.
After lots of search, I applied this solution:
In application.html.erb:
<style>
:root {
--primary-color: <%= current_client.header_color %>;
--body-color: <%= current_client.footer_color %>;
}
</style>
In variables.sass:
$primary: var(--primary-color);
And boom you are good to go!
Reference: https://medium.com/angular-in-depth/build-truly-dynamic-theme-with-css-variables-539516e95837
To make a dynamic variable is not possible in SASS as of now, since you will be adding/connecting another var that needs to be parsed once when you run the sass command.
As soon as the command runs, it will throw an error for Invalid CSS, since all your declared variables will follow hoisting.
Once run, you can't declare variables again on the fly
To know that I have understood this, kindly state if the following is correct:
you want to declare variables where the next part (word) is dynamic
something like
$list: 100 200 300;
#each $n in $list {
$font-$n: normal $n 12px/1 Arial;
}
// should result in something like
$font-100: normal 100 12px/1 Arial;
$font-200: normal 200 12px/1 Arial;
$font-300: normal 300 12px/1 Arial;
// So that we can use it as follows when needed
.span {
font: $font-200;
p {
font: $font-100
}
}
If this is what you want, I am afraid as of now, this is not allowed