mixins with multiple params - how properties are selected? - less

I'm reading a manual on LESS and there is the following example here:
It is legal to define multiple mixins with the same name and number of parameters. Less will use properties of all that can apply. If you used the mixin with one parameter e.g. .mixin(green);, then properties of all mixins with exactly one mandatory parameter will be used:
.mixin(#color) {
color-1: #color;
}
.mixin(#color; #padding:2) {
color-2: #color;
padding-2: #padding;
}
.mixin(#color; #padding; #margin: 2) {
color-3: #color;
padding-3: #padding;
margin: #margin #margin #margin #margin;
}
.some .selector div {
.mixin(#008000);
}
compiles into:
.some .selector div {
color-1: #008000;
color-2: #008000;
padding-2: 2;
}
I can't seem to grasp the logic behind selecting the properties. Can someone please explain me it?

Quoting the LESS Manual:
It is legal to define multiple mixins with the same name and number of parameters. Less will use properties of all that can apply. If you used the mixin with one parameter e.g. .mixin(green);, then properties of all mixins with exactly one mandatory parameter will be used
The key statements are Less will use properties of all that can apply and then properties of all mixins with exactly one mandatory parameter will be used.
In the below sample, the output contains both the properties specified within the .mixin with one parameter as well as the .mixin with two parameters because the .mixin with two parameters has a default value for the second one (and hence is in need of only one mandatory parameter).
.mixin(#color; #padding:2) {
color-2: #color;
padding-2: #padding;
}
So in essence, when the second parameter is not specified in the mixin call statement, the rule/properties can still be applied because the default value would be used. If you remove the default value for the padding and make it like below, it would not get applied when the mixin call has only one parameter.
.mixin(#color; #padding) {
color-2: #color;
padding-2: #padding;
}
Similarly, the .mixin with three parameters is not applied because the mixin call has only one parameter and there is a default value specified for only one other parameter. So in essence, we have only two parameters with values.
.mixin(#color; #padding; #margin: 2) {
color-3: #color;
padding-3: #padding;
margin: #margin #margin #margin #margin;
}

A supplement to the nice #Harry answer above, just the same thing but in other words (I just thought this "compact" form would work the best for a "technician" guy like me for example):
.mixin(#color) {...} matches only .mixin calls with 1 argument passed.
.mixin(#color; #padding: 2) {...} matches .mixin calls with 1 or 2 arguments.
.mixin(#color; #padding; #margin: 2) {...} matches .mixin calls with 2 or 3 arguments.
The .mixin(#008000); call in the example has 1 argument so only first and second mixin definitions are invoked (but not the third).

Related

Restrict mixin values and throw exception when invalid value is provided

I have the following Less mixin:
.box-sizing(#value) {
-webkit-box-sizing: #value;
-moz-box-sizing: #value;
box-sizing: #value;
}
and I would like to allow only the values 'border-box' and 'content-box' as parameter, otherwise the Less engine should throw an exception. How can I achieve this? Because without this validation I can write any value to the mixin and it will work, but it will also generate invalid CSS and no one will notice that there is an error.
As far as I know, there is no way to make the Less compiler throw an error for an invalid value like described in question. However, you can make the Less compiler not to produce any output at all when an invalid value is provided by making use of the guards feature.
In the below snippet, the mixin is invoked only when either of the two valid values are passed as input. If a different value is provided, Less compiler would find no match and hence wouldn't output anything.
.box-sizing(#value){
& when (#value=content-box) , (#value=border-box){ /* comma is the or operator */
-webkit-box-sizing: #value;
-moz-box-sizing: #value;
box-sizing: #value;
}
}
#demo{
.box-sizing(content-box);
}
Less also has some built-in type functions which can be used along with guards to check if a value is valid or not (like if the input is a number or is a color etc). There is also an iskeyword function but none of these would check for an invalid CSS value.
If you have a wide list of valid values then you could make use of the loops feature like below. Here, we extract each value from the array of valid values and if the input value matches one of them, we output the properties. If the input does not match any input value, nothing is output (again).
#valid-values: content-box, border-box, padding-box;
.box-sizing(#value){
.loop-valid-values(#index) when (#index > 0){
#valid-value: extract(#valid-values, #index);
& when (#value= #valid-value){
-webkit-box-sizing: #value;
-moz-box-sizing: #value;
box-sizing: #value;
}
.loop-valid-values(#index - 1);
}
.loop-valid-values(length(#valid-values));
}
#demo{
.box-sizing(content-box);
}
Strictly not recommended but if you insist on making the compiler throw some exception or the other when an invalid value is provided then you could deliberately introduce an error in the not-valid-value part.
.box-sizing(#value){
& when (#value= content-box), (#value= border-box){
-webkit-box-sizing: #value;
-moz-box-sizing: #value;
box-sizing: #value;
}
& when not (#value= content-box), (#value= border-box){
output: #bwahahaha; /* there is no such variable and hence when the input value is not valid, compiler will complain that variable is undefined */
}
}
#demo{
.box-sizing(conten-box);
}
You can hide actual mixin implementation under another name and provide only content-box/border-box overloads for .box-sizing:
// impl.:
.box-sizing(border-box) {.box-sizing-impl(border-box)}
.box-sizing(content-box) {.box-sizing-impl(content-box)}
.box-sizing-impl(#value) {
-webkit-box-sizing: #value;
-moz-box-sizing: #value;
box-sizing: #value;
}
// usage:
usage {
.box-sizing(content-box); // OK
.box-sizing(content-foo); // Error: No matching definition was found for `.box-sizing(content-foo)`
}
(Unlike guards, "argument pattern matching" (aka overloading) does not silently skip if not matched).
A slightly less repetitive variant of above:
.box-sizing(#value) {
.-(#value);
.-(border-box) {.ok}
.-(content-box) {.ok}
.ok() {
-webkit-box-sizing: #value;
-moz-box-sizing: #value;
box-sizing: #value;
}
}
The error message is less descriptive this way: No matching definition was found for '.-(content-foo)' so you may find it better to use .box-sizing_ or so instead of .-.
-
Though the idea of testing every mixin parameter and throwing errors if it does not fit is sort of strange. (Are you going to do this for every mixin you write? That's a lot of boredom...) You'd better to put some CSS validator/lint into your build-chain instead.
And the usual remark for vendor prefixing: stop writing mixins for this and use Autoprefixer.

Override global variables from within a mixin function

So I am trying to override some "global" variables based on a variable passed in from the php-less compiler.
I'm not sure if I am doing something wrong, or if it is just not possible due to the scope?
EDIT: I'm trying to get the background of the body to be red in this case.
external.less
// From external less stylesheet that I can't/don't want to modify
#myColour: blue;
body {
background: #myColour; // always blue
}
my.less
#import "external.less"
// My styles
.setResponsive(#responsive) when (#responsive = on) {
#myColour: green;
}
.setResponsive(#responsive) when (#responsive = off) {
#myColour: red;
}
#responsiveState: off; // actually being set from compiler
.setResponsive(#responsiveState);
div {
.setResponsive(#responsiveState);
background: #myColour; // red
}
http://codepen.io/anon/pen/gbOymJ
You are using the Mixins as Functions:
Variable defined directly in callers scope can not be overriden.
In your situation both #myColour: blue; and .setResponsive(#responsiveState); are in the same scope (the main scope). So what you are trying is not possible.
You should re-declare all the variables at the end of your code (using the same mechanism your are using to set #responsiveState )

Is there a way in LESS to remove a property if a variable does not exist?

Lets say I have a LESS file like this...
#myVariable: 5px;
.myRule {
myProperty1: #myVariable;
myProperty2: myOtherValue1;
}
I'll get this outcome when I compile this with LESS.
.myRule {
myProperty1: 5px;
myProperty2: myOtherValue1;
}
However, instead of having #myVariable defined inside this LESS file, I want to reference it from somewhere else. My referenced file, may or may not contain this variable. Currently, if the variable is missing, I'll get a result like this.
.myRule {
myProperty1: ;
myProperty2: myOtherValue1;
}
Is there any LESS functionality that would allow me to remove the property completely if the variable was not provided so that my output was like this.
.myRule {
myProperty2: myOtherValue1;
}
I've looked through the language features of LESS and couldn't find anything that does this. Maybe I'm missing something?
This would be the syntax that I'm imagining, but I'm pretty sure this doesn't exist.
.myRule {
when(exists(#myVariable)) {
myProperty1: #myVariable;
}
myProperty2: myOtherValue1;
}
Set Default and Override
//Load in a master variable file for all LESS
//containing all "possible" variables that may be used
//but set to some default values that "hide" the properties
//if the variable does not exist (such as "false" here)
#myVariable: false;
//Load in your specific variable references from elsewhere
//Individual variable may/may not be defined in this file
//but if one is defiend, this value overrides the previous value
#myVariable: 5px;
//Define a mixin to activate the setting of the property
//only if the value is not the original default hiding value
.setIfValue(myVariable) when not (#myVariable = false) {
myProperty1: #myVariable;
}
//Use the mixin to conditionally set the value into other mixins
.myRule {
.setIfValue(myVariable);
myProperty2: myOtherValue1;
}
Default Output
.myRule {
myProperty2: myOtherValue1;
}
Overridden Output
.myRule {
myProperty1: 5px;
myProperty2: myOtherValue1;
}
I think you made the syntax too complex. It should be something like this:
.myRule {
& when (#myVariable = false) {
myProperty1: #myVariable;
}
myProperty2: myOtherValue1;
}
This feature (apparently) is called CSS Guards.
It seems like you can check if a variable has a specific value, but not whether this allows you to check for defined or not.
So, I guess the other file should always define this variable as a prerequisite, but it can set it to 'false' (or any kind of default) to indicate that this property should not be used at all.

LESS: Change on-the-fly passed parameter through Mixin call

I have a LESS Mixin called in different stylesheets (i.e. one for each breakpoint controlled by media queries). In each stylesheet it is called with different parameter value such as:
"mobile.less":
.mixin(1);
"tablet.less":
.mixin(2);
.....
The mixin is defined (for example) as:
.mixin(#parameter) when (#parameter = 1)
{
body
{
font-style:italic;
}
.......
}
.mixin(#parameter) when (#parameter = 2)
{
.......
}
I'm developing a demo website so I wish that users could change this values on-the-fly changing value that is passed as parameter using a form field.
I tried the following method:
less.modifyVars({'#parameter' : <value from form field>});
But it would work only if #parameter is a "global" variable, not a passed parameter through the call...... Is there a method to change also passed parameters?
Thank you.
Original Answer
Yes, just make the variable outside the scope of the mixins but still use it in the guard expression. Something like:
#parameter: 1;
.mixin() when (#parameter = 1) {
body {
font-style:italic;
}
}
.mixin() when (#parameter = 2) {
body {
font-style:normal;
}
}
.mixin();
This generates the 1 code. If the variable gets set to 2, it generates the 2 code, etc.
Discussion of "re-process on the fly called mixins"
With reference to your comment, to my knowledge there is no way to directly re-process the local variable of a mixin call without doing something to the mixin definition itself to allow for it. So in your example, if "mobile.less" has a .mixin(1); call, how can you reprocess it to be, say, .mixin(4) based on user input. If you have not set up the call with a variable to begin with, then there is not way to "modify" the 1 in the original call. However, setting up with a variable call to begin with is really just a longer version of the answer I give above. Consider that this code essentially does the same as the above, only with more coding involved:
LESS Mixins Defined
.mixin(#parameter) when (#parameter = 1) {
body {
font-style:italic;
}
}
.mixin(#parameter) when (#parameter = 2) {
body {
font-style:normal;
}
}
Calls it in Files
//mobile.less
#parameter: 1;
.mixin(#parameter);
//tablet.less
#parameter: 2;
.mixin(#parameter);
Notice that we are still working with a "global" #parameter variable that is just being passed in as a "local" variable of the same name to the mixins. So all we gain here is more code (the addition of the local variable) to do the same thing.
Now assuming you are really after modifying the final output css behavior through the user input, then you may be able to "override" by a later call. This assumes that all the same properties, selectors are set by the various mixin calls, just to different values. So let us assume .mixin(1) is still in "mobile.less", you could set up a "reset.less" file that is called on user input to override by the css cascade.
LESS Mixin Definition Added
.mixin(#parameter) when (#parameter = 0) {
//purposefully empty, used as default for reset.less
}
Calls in your current "mobile.less" etc. remain as they are. You can have a global value of #parameter: 0; set in your global "variables.less" file, and then "reset.less" is this:
//reset.less
.mixin(#parameter);
That way "reset.less" outputs nothing by default (using the mixin definition just done above). This "reset.less" file is put last in the html processing so that it follows any "mobile.less" stylesheets, etc. Then, when the user changes #parameter, the "reset.less" is updated with the new values, and it does output css, which, by virtue of the css cascade, overrides the values of "mobile.less" etc.

How may I pass a variable to :extend when using Less?

I would like to use a variable within a Less mixin that when passed to :extend has the same result as if I had instead used :extend with a class name string.
In the example below I have commented out a line that produces the CSS output I want by using :extend with a string.
But how can I do this with the #class-name variable instead?
.class-to-be-extended {display: block;}
.my-mixin (#class-name) {
#class-string: ~".#{class-name}";
.my-extra-class {
// &:extend(.class-to-be-extended); // works
&:extend(#{class-string}); // doesn't work, but no errors
}
}
.my-mixin(class-to-be-extended);
The CSS output I would like is:
.class-to-be-extended,
.my-extra-class {
display: block;
}
At the time of writing this I'm using the latest version I can find which is Less 1.4.2
This is not possible with Less version 1.4.2. (Thanks to Jon Schlinkert for letting me know via Twitter).
A feature request has been submitted to https://github.com/less/less.js/issues