Restrict mixin values and throw exception when invalid value is provided - less

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.

Related

Is it possible to perform some operations on the & selector inside a LESS mixin?

I'm trying to perform some string operations on the current selector & while inside a LESS mixin, but no matter what I try, I'm getting the & substitution character instead of the actual selector query. Is there a way to get the actual selector somehow or does the queue of operations in LESS prohibit doing that?
.mixin() {
#current: &;
test: ~`console.log("#{current}"), "#{current}"`;
}
.thing {
display: block;
.mixin();
}

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 )

mixins with multiple params - how properties are selected?

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).

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.

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