Resolving dynamic variables in LESS - variables

I am trying to generate a number of classes in a loop based on a number of pre-defined variable snippets.
I have a variables.less document that I am importing at the top of this less file containing my color variables. I then want to generate matching classes for these, but I am unable to get less to compile the variable.
My code:
.loop-class(~"primary", ~"success", ~"info", ~"warning", ~"danger";);
.loop-class(#list, #index: 1) when (isstring(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
color: ~'#button-#{status}';
}
.loop-class(#list, (#index + 1));
}
Which compiles to:
.button-primary {
color: #button-primary;
}
.button-success {
color: #button-success;
}
etc etc
As you can see, I get the variable name to concatenate correctly, but I can not get it to resolve, so I'm guessing that LESS has already done it's variable compilation before getting to this function?
I have already tried moving the variables into this document, as well as wrapping the variables in a mixin and adding that inside the .loop-class, but neither of these seemed to help.
I also tried something like:
#status: extract(#list, #index);
#compileClass: ~'#button-#{status}';
.button-#{status} {
color: #compileClass;
}
where I am saving the variable in a another one and then referencing that, but it yields the same result.
I looked at less css calling dynamic variables from a loop and tried implementing that as follows:
.loop-class(~"primary", ~"success", ~"info", ~"warning", ~"danger";);
.define(#var) {
#fallback: ~'#button-#{var}';
}
.loop-class(#list, #index: 1) when (isstring(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
.define(#status);
color: ##fallback;
}
.loop-class(#list, (#index + 1));
}
But that gave me the error that ##button-danger (last in the index) is undefined, so it still can't resolve the variable.
Is it obvious to you guys what I'm doing wrong?
Thanks for your help!

Missing Brackets
You are missing a set of needed brackets to resolve the variable:
LESS
//imported from another file
#button-primary: cyan;
#button-success: green;
#button-info: orange;
#button-warning: yellow;
#button-danger: red;
//in your mixin file
.loop-class(~"primary", ~"success", ~"info", ~"warning", ~"danger";);
.loop-class(#list, #index: 1) when (isstring(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
color: ~'#{button-#{status}}'; /* two more brackets needed */
| |
here here
}
.loop-class(#list, (#index + 1));
}
CSS Output
.button-primary {
color: #00ffff;
}
.button-success {
color: #008000;
}
.button-info {
color: #ffa500;
}
.button-warning {
color: #ffff00;
}
.button-danger {
color: #ff0000;
}
Cleaner More Friendly Code
Also, as a matter of less cluttered and more user friendly code, you can remove your multiple string interpolations needed for the mixing call by changing isstring to iskeyword in your mixin:
.loop-class(primary, success, info, warning, danger;); /* cleaner code on call */
.loop-class(#list, #index: 1) when (iskeyword(extract(#list, #index))) {
#status: extract(#list, #index);
.button-#{status} {
color: ~'#{button-#{status}}';
}
.loop-class(#list, (#index + 1));
}

Related

SASS / SCSS: Interpolate variable from string / name

Is it possible to get a variable by name?
I tried building the following function, but it's not working as expected...
#function variable-lookup($variable, $suffix: "") {
$value: null;
#if ($suffix != "" and global-variable-exists($variable+"-"+$suffix)) {
$value: #{$variable+"-"+$suffix};
}
#else if (global-variable-exists($variable)) {
$value: #{$variable};
}
#return $value;
}
Here's an example of how it might be used:
$primary: #000;
$primary-hover: blue;
a {
color: variable-lookup("primary", "base");
&:hover {
color: variable-lookup("primary", "hover");
}
}
The real power would come in when I want to write a bunch of context-specific, shorthand wrapper functions around this "variable-lookup" function.
Any ideas how to achieve this?
Trying to interpolate #{$variable+"-"+$suffix} to give the value primary-base and further trying to get the value of same variable name is not possible. primary-base is already a value and and can't be interpreted as a variable name. That sort of thing could lead to a lot of chaos.
For what you want to accomplish, you are better of using a map and checking for the key in that map
$colours: (
'primary': red,
'primary-base': blue
);
#function variable_lookup($colour, $suffix: '') {
$value: null;
#if ( $suffix != '' and map-has-key($colours, unquote($colour+'-'+$suffix)) ) {
$value: map-get($colours, unquote($colour+'-'+$suffix));
} #else if ( map-has-key($colours, unquote($colour)) ) {
$value: map-get($colours, unquote($colour));
}
#return $value;
}
div {
color: variable-lookup(primary, base);
}
p {
color: variable-lookup(primary);
}
This compiles to the following css
div {
color: blue; }
p {
color: red; }
Your code stored colours as variables but I used those names as keys in maps
This allowed to simulate the checking of variables in your code using the map-has-key method. If that returns true, the key exists and we can get the value which in this case would be the colour using map-get
UPDATED ANSWER
One way to address the issues you raised in your comments would be to define the variables and use them in as values in the map
$primary: #fff;
$warning: yellow;
$colours: ( primary: $primary,
primary-hover: darken($primary, 5%),
secondary: $warning,
secondary-hover: darken($warning, 5%) );
Another way would be to iterate through two lists and map colour to a style
$colours: ();
$list: primary success warning; //map primary to blue, success to green and so on
$shades: blue green yellow;
#for $i from 1 through length($list) {
$key: nth($list, $i);
$value: nth($shades, $i);
$colours: map-merge($colours, ($key: $value));
$colours: map-merge($colours, (unquote($key+'-hover'): darken($value, 5% )) );
}
#debug $colours // (primary: blue, primary-hover: #0000e6, success: green, success-hover: #006700, warning: yellow, warning-hover: #e6e600)
The variable_lookup function remains the same.
Hope this is able to help

Passing a variable list through .for loop using Less 2.6.1 is unable to compile

I have been using a Less mixin which makes looping in Less, less verbose and is really handy: https://github.com/seven-phases-max/less.curious/
I upgraded from Less 2.6.0 to Less 2.6.1 and now the Less will not compile.
Here's the Less code:
// ............................................................
// .for
.for(#i, #n) {.-each(#i)}
.for(#n) when (isnumber(#n)) {.for(1, #n)}
.for(#i, #n) when not (#i = #n) {
.for((#i + (#n - #i) / abs(#n - #i)), #n);
}
// ............................................................
// .for-each
.for(#array) when (default()) {.for-impl_(length(#array))}
.for-impl_(#i) when (#i > 1) {.for-impl_((#i - 1))}
.for-impl_(#i) when (#i > 0) {.-each(extract(#array, #i))}
#gray-base: #000;
#gray-darker: #222;
#gray-dark: #333;
#gray: #555;
#gray-light: #777;
#gray-lighter: #eee;
#gray-type:
gray,
gray-base,
gray-dark,
gray-darker,
gray-light,
gray-lighter;
.gray-type {
// Mixin to generate the gray font colours for p tags in the font stacks.
.for(#gray-type); .-each(#name) {
.#{name} {
color: ##name;
}
}
}
Here's the expected output:
.gray-type .gray {
color: #555;
}
.gray-type .gray-base {
color: #000;
}
.gray-type .gray-dark {
color: #333;
}
.gray-type .gray-darker {
color: #222;
}
.gray-type .gray-light {
color: #777;
}
.gray-type .gray-lighter {
color: #eee;
}
After upgrading to Less 2.6.1 I get the following error:
>> SyntaxError: variable #[object Object],[object Object] is undefined in less/style.less on line 41, column 3:
>> 40 // Mixin to generate the gray font colours for p tags in the font stacks.
>> 41 .for(#gray-type); .-each(#name) {
>> 42 .#{name} {
Warning: Error compiling less/style.less Use --force to continue.
I think the problem is related to the hyphens being stripped from the variables during compilation, therefore making the variables passed though the list invalid.
Any help would be greatly appreciated.

Less CSS, please explain Advanced arguments and the #rest variable to me?

Simple question: I don't understand what the Advanced arguments do in Less CSS, as per http://lesscss.org/features/#mixins-parametric-feature-advanced-arguments-and-the-rest-variable . I've battled to get my head around how it's explained there.
I understand this:
.mixin(#a: 1) {
But I don't understand the following two, where the ... is introduced:
.mixin(...) { // matches 0-N arguments
.mixin() { // matches exactly 0 arguments
.mixin(#a: 1) { // matches 0-1 arguments
.mixin(#a: 1; ...) { // matches 0-N arguments
.mixin(#a; ...) { // matches 1-N arguments
.mixin(#a; #rest...) { // #rest is bound to arguments after #a
// #arguments is bound to all arguments }
I'm learning Less because I'm very keen on bootstrap, but this puzzled me.
Thank you very much!
Well, okay you should also read http://lesscss.org/features/#mixins-parametric-feature-pattern-matching.
In Less only mixin that match the number of arguments of the caller are compiled. Notice also that when two or more mixins match, all of them are compiled into CSS.
When you mixin got one argument, like that shown below:
.mixin(#a) {}
Only callers with one argument match and will be compiled: .mixin(3); or .mixin(1) and so on. But NOT .mixin() or .mixin(1,2,3)
When you set a default value for the first argument, for instance 3, as shown below:
.mixin(#a: 3) {}
Now not only calls with 1 argument match, but also calls with zero arguments:
.mixin(#a: 3) {property: #a;}
p{ .mixin();}
outputs:
p {
property: 3;
}
Now take a look to the special ... argument, that argument matches any number of arguments. So .mixin(...) will match and get compiled the following callers .mixin(), .mixin(1) and .mixin(1,2,3,4).
When you prepend a name (including the #) to the ... argument the values will be assigned to a variable with that name:
.mixin(#listofvariables...) {
p: #listofvariables;
}
p {
.mixin(one; two; three);
}
outputs:
p {
p: one two three;
}
Notice that ... assigns the arguments to a list, which can be manipulated with the list functions too.
An mixin such as .mixin(#a; ...) is a variant of the preceding two use cases. This mixins requires a first argument set, followed by zero or any other arguments.
#arguments is a special variable that contains a list of all argument of the mixin:
.mixin(#a; #b) {p1: #arguments; p2:extract(#arguments,2); p3:#b;}
p {.mixin(1; 2);}
outputs:
p {
p1: 1 2;
p2: 2;
p3: 2;
}
So the #arguments variable can be used in any mixin and does not require an ... argument.
What would a caller for a mixin like this look like? .mixin(#a; ...)
could it be something like this: .mixin(#a,53px); ? How does it
determine where the 53px goes to?
The 53px is not assigned to a variable, but it is the second item of the #arguments list. You can get it by extract(#arguments,2).
An use case for the .mixin(#a; ...) {} can be to assign a property always when .mixin() regardless the number of arguments, example:
.mixin(#a; ...) { color: #a;}
.mixin(#a) { background-color: contrast(#a); width:100%;}
.mixin(#a; #b;) { background-color: contrast(#a); width:#b;}
div {
.mixin(red);
}
div.small {
.mixin(red,50%);
}
outputs:
div {
color: red;
background-color: #ffffff;
width: 100%;
}
div.small {
color: red;
background-color: #ffffff;
width: 50%;
}
notice that the .mixin(#a; #rest...) {} assigns 35px the first item of the #rest list. And so the following Less code:
.mixin(#color,#padding...) {
color: #color;
padding: #padding
}
div {
.mixin(red; 10px; 20px; 5px; 5px);
}
outputs:
div {
color: red;
padding: 10px 20px 5px 5px;
}

Guards with multiple conditions/Nested guards

I am trying to make a mixin which evaluates both the parameters typ and compare values.
Say I have a mixin to create a CSS3 gradient with fallbacks for older browsers but if no start and/or end color is entered only output the background-color. In addition to checking all colors are being entered correctly I want to make sure that neither the start or end color is equal to the fallback color.
This is how I would like to write it using standard logic but I am not allowed to nest the guards together.
.gradient(#color, #start: 0, #stop: 0) when (iscolor(#color)) and (iscolor(#start)) and (iscolor(#stop)) and not ((#start = #color) and (#stop = #color)) {
background: #color;
background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #start),color-stop(1, #stop));
additional-browser-specific-prefixes;
}
.gradient(#color, #start: 0, #stop: 0) when (default()) {
background-color: #color;
}
Essentially I want to do the following Javascript condition: if(iscolor(color) && iscolor(start) && iscolor(end) && (start !== color && end !== color)). Does anyone have any clue if this is possible?
Any ideas would be greatly appreciated.
Less guards should have the same implementation as CSS #media (this maybe only apply for the syntax??). I can not find examples for the #media, which use the kind of nesting for operators you try to use. So it is not possible for CSS #media and so also not possible for Less guards?
But, on http://mdn.beonex.com/en/CSS/Media_queries.html i found:
The not operator has a very low precedence. For example, the not is
evaluated last in the following query:
#media not all and (-moz-windows-compositor) { ... }
This means that the query is evaluated like this:
#media not (all and (-moz-windows-compositor)) { ... }
... rather than like this:
#media (not all) and (-moz-windows-compositor) { ... }
This should mean that you do not have to wrap in extra parentheses your conditions after the not keyword. The following code should work:
.gradient(#color, #start: 0, #stop: 0) when (iscolor(#color)) and (iscolor(#start) and not #start = #color) and (#stop = #color), but unfortunately this does not works as expected.
If the operator precedence of Less guards have to equal to the operator precedence of the CSS #media, this could be considered as a bug maybe.
update My above assumption was wrong, the not keyword will be applied on the whole media query (or guard) only, so not (a), not (b) make no sense at all. Also see: https://github.com/less/less.js/issues/2149
If all the above is truth, try to revert the conditions:
.gradient(#color, #start: 0, #stop: 0)
when (#start = #color) and (#stop = #color), not (iscolor(#color)), not (iscolor(#start)), not (iscolor(#stop)) {
background-color: #color;
}
.gradient(#color, #start: 0, #stop: 0)
when (#start = #color) and (#stop = #color), (iscolor(#color)=false), (iscolor(#start)=false), (iscolor(#stop)=false) {
background-color: #color;
}
.gradient(#color, #start: 0, #stop: 0) when (default()) {
background: #color;
background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #start),color-stop(1, #stop));
additional-browser-specific-prefixes;
}
or try to use nested mixins as follows:
default(#a,#b,#c){
property: default;
}
.fallback(#a,#b,#c){
property: fallback;
}
.background(#a,#b,#c) when (#a>0) and (#b>0) and (#c>0)
{
.and(#a,#b,#c) when (#a=#c) and (#b=#c) {
.fallback(#a,#b,#a);
}
.and(#a,#b,#c) when (default()){
.default(#a,#b,#a);
}
.and(#a,#b,#c);
}
.background(#a,#b,#c) when (default())
{
.fallback(#a,#b,#c);
}
test0 { .background(0,1,1); }
test1 { .background(1,1,1); }
test2 { .background(2,1,1); }
test3 { .background(1,2,1); }
test4 { .background(1,1,2); }

Define LESS variable from string

I'm trying to write a mixin that returns a variable for converting px to em. I've toyed with a few things, but ultimately i'd like to call a mixin and get a return value similar to SASS's functions. Based on on return values here: http://www.lesscss.org/#-return-values, I can only define a variable once as a return value. Example:
Mixin
.px-to-emz( #size, #base: #font-size-base ){
#em: round( unit( (#size / #base), ~"em" ), 3 );
}
Call it:
.foo {
font-size: #em;
.px-to-emz(10, 16px);
height: #em;
.px-to-emz(200, 16px);
}
Fine, if you want to only return one variable, but if i want to return multiple variables I need to define new variable names. Here's what i'd ideally like to do
Mixin:
.px-to-ems( #size, #var: 'em', #base: #font-size-base ){
~'#{var}': round( unit( (#size / #base), ~"em" ), 3 );
}
Call it:
.foo {
font-size: #font-size;
.px-to-ems(10, 'font-size', 16px);
height: #height;
.px-to-ems(200, 'height', 16px);
}
#1
So far the best known solution for this problem is to put each mixin call into its own scope:
.px-to-ems(#size, #base: #font-size-base) {
#-: round(unit((#size / #base), em), 3);
}
.foo {
.-() {font-size: #-; .px-to-ems( 10, 16px)}
.-() {height: #-; .px-to-ems(200, 16px)}
.-;
}
Replace #- and .- with whatever identifiers you find suitable.
#2
The other way around is to use recently added (Less 1.6.x) property interpolation feature:
.px-to-ems(#property, #size, #base: #font-size-base) {
#{property}: round(unit((#size / #base), em), 3);
}
.foo {
.px-to-ems(font-size, 10, 16px);
.px-to-ems(height, 200, 16px);
}
It's more clean than #1 if you simply need to assign the "function" result to a property.