Can you set a variable inside a mixin in LESS CSS? - less

I understand LESS doesn't have if/else structure and instead relies on guarded mixin statements. It doesn't appear to be able to set a LESS CSS variable inside of a mixin though.
Anyone have an idea of how I could implement something to the effect of.
if( ispercentage( #height ) {
#height: "1024px";
}
// Calculations dependent on #height
#textAreaHeight = 0.5 * #height;
#buttonHeight = 0.2 * #height;
I've looked at several other stackoverflow questions including:
LESS CSS - Setting variable within mixin

Yes, it can be done. There was one bug that needed to be worked around.
Example LESS Code
//Set up guarded mixins
.setHeight(#h) when (ispercentage(#h)) {
#height: 1024px;
}
.setHeight(#h) when not (ispercentage(#h)) {
#height: #h;
}
//set up main height
#mainHeight: 50%;
body { height: #mainHeight;}
//call it by itself to make global
//.setHeight(#mainHeight); <-this failed (appears to be a bug)
.setHeight(50%); // <-this worked
.subsection { height: #height; /* just to show it is setting it */}
//use it for other globals
#textAreaHeight: 0.5 * #height;
#buttonHeight: 0.2 * #height;
textarea { height: #textAreaHeight}
button { height: #buttonHeight}
//override it locally
body.fixedHeight {
.setHeight(300px);
#textAreaHeight: 0.333 * #height;
height: #height;
textarea { height: #textAreaHeight}
}
Example CSS Output
body {
height: 50%;
}
.subsection {
height: 1024px; /* just to show it is setting it */
}
textarea {
height: 512px;
}
button {
height: 204.8px;
}
body.fixedHeight {
height: 300px;
}
body.fixedHeight textarea {
height: 99.9px;
}

You can create a mixin that is "guarded" by the type, e.g.
.doHeightSet(#height) when ispercentage(#height) {
.doheightSet(1024px)
}
.doHeightSet(#height) when not ispercentage(#height) {
// Calculations dependent on #height
#textAreaHeight: 0.5 * #height;
#buttonHeight: 0.2 * #height;
/* use variables here */
}
Sorry, I don't have time to try this out, so I may have made a syntax mistake.
edit corrected syntax.

Related

Less variables in comments

I need to generate some CSS with some comments right above the classes and in those comments, I need to evaluate some variables. I've been successful in doing this in Sass but Less doesn't seem to have the same functionality.
Here's what I need:
/**Header*/
.Header {
font-size: 1.5em;
}
Here's my attempt in Sass:
#function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
#if $index {
#return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
#return $string;
}
#mixin rte_property($name) {
/**#{$name}*/
.#{str-replace($name, ' ', '')} {
#content;
}
}
#include rte_property(Header) {
font-size: 1.5em;
}
Here's my attempt in Less:
.rte_element (#name, #rules) {
#className: e(replace(#name, " ", ""));
/**#{name}*/
.#{className} {
#rules();
}
}
.rte_element("Header 2", {
font-size: 1.5em;
});
Is it possible for Less to interpolate/evaluate variables in comments? If so, how?
There is no straight-forward (non-hacky) way to achieve this in Less. Less compiler does not evaluate any variable that is present within comments and so it would continue to be printed as #{var} instead of the evaluated value.
However, that doesn't mean there is no way at all. There is a way of achieving something close. That would be to put the entire comment text into a temporary variable and print it before the selector using selector interpolation technique.
The comment would not cause any impact to how the compiled CSS works (because the UA will just ignore the comments, refer the snippet at the end - it uses the compiled CSS produced by this code) but it doesn't have a line-break.
Note: I would definitely not recommend implementing such hacky solutions. I have given it here just to show that it can be done in a different way.
Less Code:
.rte_element(#name, #rules) {
#className: e(replace(#name, " ", ""));
#comment: ~"/* #{name} */"; /* store the comment structure as a variable */
#{comment} .#{className} { /* print it before the selector */
#rules();
}
}
.rte_element("Header 2", {
font-size: 1.5em;
color: red;
});
.rte_element("Header 3", {
font-size: 1.75em;
color: blue;
});
Demo with compiled CSS:
/* Header 2 */ .Header2 {
font-size: 1.5em;
color: red;
}
/* Header 3 */ .Header3 {
font-size: 1.75em;
color: blue;
}
<div class="Header2">Header 2 text</div>
<div class="Header3">Header 3 text</div>
Code for a line break after comment:
This is even more hacky but it seems to work in the latest compiler.
.rte_element(#name, #rules) {
#className: e(replace(#name, " ", ""));
#comment: ~"/* #{name} */
" ; /* note how there is a line break inside the quotes */
#{comment} .#{className} {
#rules();
}
}

Less adds unexpected space to variable

For some reason, the output of nth child is rendered with un unexpected space. Can anyone help?
Renders:
// Part of render
body.domain-bsci-fta-local #block-domain-switcher ul li:nth-child( 3) {
background-color: #e14313;
}
From code:
// Variables
#a-primary: #018f9e;
#b-primary: #2b6a7c;
#c-primary: #e14313;
#d-primary: #009966;
#domain-a: 'a-local';
#domain-b: 'b-fta-local';
#domain-c: 'c-fta-local';
#domain-d: 'd-fta-local';
#domains: #domain-a #a-primary 1, #domain-b #b-primary 2, #domain-c #c-primary 3, #domain-d #d-primary 4;
// Call
body {
.generate-menus();
}
// Functions
.generate-menus() {
.for(#domains);
.-each(#domain) {
#dn: e(extract(#domain, 1));
#dc: extract(#domain, 2);
#dr: extract(#domain,3);
.generate-menu(#dn, #dc, #dr);
}
}
.generate-menu(#domainname, #domaincolor, #domaincount) {
&.domain-#{domainname} {
#block-domain-switcher {
ul {
li {
&:nth-child(#{domaincount}) {
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
}
}
.navigation .submenu {
background-color: #domaincolor;
}
}
}
// ............................................................
// .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))
}
Note: As mentioned by seven-phases-max in his comments to the question, this was a bug which has already been fixed in v2.x. Leaving this answer (with the work-around solution) as-is to help future readers who can't upgrade their compiler for whatever reason.
The problem happens only for selectors which use selector interpolation and are nested within one or more parent selectors. It can be solved by using a temporary variable which contains the pseudo-selector like below: (it uses escaped string feature)
Option 1:
ul {
li {
#selector: ~":nth-child(#{domaincount})"; /* the selector is formed here */
&#{selector} { /* and used here */
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
}
Option 2:
li {
#count: ~"(#{domaincount})";
&:nth-child#{count} { /* and used here */
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
Sample Compiled Output:
body.domain-a-local #block-domain-switcher ul li:nth-child(1) {
background-color: #018f9e;
}
Related Links:
concatenate values in less (css) without a space
Redudant space in interpolated selectors like nth(...)
As mentioned above and in the linked issue thread, the issue happens only when the selector is formed using selector interpolation and is nested under one or more parents.
This works
// Variables
#list: a 1;
#num: extract(#list, 2);
// Usage
body div:nth-child(#{num}) {
color: #444;
}
But this doesnt
// Variables
#list: a 1;
#num: extract(#list, 2);
// Usage
body {
div:nth-child(#{num}) {
color: #444;
}
}

Less mixin calling another mixin won't generate nested rules

I have a Less project set up with three files relevant to my question.
A config file which just holds variables and their values.
A controller file which my Less watcher is told to compile.
A mixin file, titled _buttons.less, which I import as a reference in the
controller file.
Inside the controller file, I have a rule:
.bananas {
.nesting-mixin(#color-list);
}
Inside the variable file, I have a list:
#color-list : red, blue, green, black;
Inside the mixin file, I have two mixins:
.do-the-mixin(#list) {
color: extract(#list, 1);
&.blue {
color: extract(#list, 2);
}
&.green {
color: extract(#list, 3);
}
}
.nesting-mixin(#list) {
&.colored {
.do-the-mixin(#list);
}
}
When I call the .nesting-mixin method from the controller file, the output is:
.bananas.colored {
color: #ff0000;
}
But when I move the nesting mixin into the controller file, I get:
.bananas.colored {
color: #ff0000;
}
.bananas.colored {
color: #ff0000;
}
.bananas.colored.blue {
color: #0000ff;
}
.bananas.colored.green {
color: #008000;
}
Is there some aspect of Less mixin importing or nesting that I don't understand? The second output is what I want, but I don't understand why it needs to be in the same file from which it's called.
Thanks for reading. :)

LESS Preprocessing and null arguments?

This might be difficult to explain. Is there a way to have less not write out the #child argument without overloading the mix-in? I really don't want two mix-ins. If I use "" double quotes are outputted. I would like the LESS compiler to leave it blank.
LESS CODE
.build-on(size, #child)
{
&--1-1 #{child}
{
width: 100%;
}
&--1-2 #{child}
{
width: 50.0%;
}
&--1-3 #{child}
{
width: 33.3%;
}
&--1-4 #{child}
{
width: 25.0%;
}
&--1-5 #{child}
{
width: 20.0%;
}
}
// I might need to provide a child element
.data-table
{
.build-on(size, table);
}
// I might not
.grid
{
.build-on(size, "");
}
Pass it like so:
.yourClass
{
.build-on(size, ~'');
}
Or Better Yet...
Define a default: .build-on(size, #child: ~'') { ... } then no second is needed:
.yourClass
{
.build-on(size);
}

How to pass a property name as an argument to a mixin in less

I want to make a function/mixin that will make a color darker if it is already dark but lighter when it is light (normalize/extremeize?)
Is it possible to do this by passing a property name (color, background-color, border-right-color, etc)?
.normalize(#color, #amount, #prop: "color") when (lightness(#color) >= 50%)
{
#prop:lighten(#color, #amount);
}
.normalize(#color, #amount, #prop: "color") when (lightness(#color) < 50%)
{
#prop:darken(#color, #amount);
}
This is currently a feature request on less.js github. So look out for it in less.js 1.4.. until then you can hack it like so...
.mixin(#prop, #value) {
Ignore: ~"a;#{prop}:#{value}";
}
Not very nice and you get an extra property but its the only way at the moment.
Guarded Mixins should be what you are looking for, however you can not use variables to define properties, only their values. So you can do it like this:
.normalize(#color, #amount) when (lightness(#color) >= 50%)
{
color:lighten(#color, #amount);
}
.normalize(#color, #amount) when (lightness(#color) < 50%)
{
color:darken(#color, #amount);
}
So this:
.class1 {
.normalize(#ddd, 10%);
}
Will output this:
.class1 {
color: #f7f7f7;
}
But you can not actually pass a property name as a variable. This is a limitation of LESS unfortunately, and while I've seen ways around it for things like margin direction, there is not a way to just pass any ol' property using a variable.
In the corresponding issue on Less' GitHub there is a workaround suggested by cloudhaed:
.blah () { color: black } // All blahs
.blah(right) { padding-right: 20px } // Right blahs
.blah(left) { padding-left: 20px } // Left blahs
#side: left;
.class { .blah(#side) }
Output
.class { color: black; padding-left: 20px;}
Maybe this will do?
This feature was added since v1.6.0:
#property: color;
.widget {
#{property}: #0ee;
background-#{property}: #999;
}
Compiles to:
.widget {
color: #0ee;
background-color: #999;
}
See http://lesscss.org/features/#variables-feature-properties