Is it possible to mix-in from mixin function parameter? - less

Basically, I want to be able to loop through break points creating, say centre-block-* classes.
Code:
.centre-block {
margin: 0 auto;
}
#breakpoints: xs 479px, sm 767px, md 991px, lg 1200px;
.make-classes(centre-block, #breakpoints);
.make-classes(#prefix, #list) {
.iter(length(#list));
.iter(#i) when (#i > 0) {
.iter(#i - 1);
#pair: extract(#list, #i);
#key: extract(#pair, 1);
#value: extract(#pair, 2);
#media (max-width: #value) {
.#{prefix}-#{key} {
.#{prefix};
}
}
}
}
From that, what I want to be able to do is add a class, say centre-block-sm that, when the screen size is under 768px the .centre classes margin: 0 auto will be applied. But I want to be able to do this for numerous class types, like adding a no-padding-* class list.
Everything works except for the fact it will not use the .#{prefix}, and instead simply doesn't recognise it. If I replace .#{prefix} with .centre-block it works. Is there a way around this, perhaps a different approach to the problem?
I feel like this sort of functionality is what LESS was designed for, so I might just be missing the point altogether.

What you're trying to do is currently not possible with Less. You can find this discussed in this thread. Though there is not much explanation in this, you can see that other linked thread within this (#1133) which mentions that this functionality is not yet implemented.
You could have a look at using detached rulesets as an alternative. In your code, convert the top level class selector (.centre-block) into a detached ruleset, store it as a variable and then pass it as one parameter to the mixin. We can then invoke this ruleset within the mixin call and it will print the content of the ruleset as it is into whichever selector we need. Below is a sample:
#centre-block : {margin: 0 auto;}; /* a detached ruleset */
#breakpoints: xs 479px, sm 767px, md 991px, lg 1200px;
.make-classes(centre-block, #breakpoints, #centre-block); /* pass the extra param */
.make-classes(#prefix, #list, #ruleset) { /* add the extra param */
.iter(length(#list));
.iter(#i) when (#i > 0) {
.iter(#i - 1);
#pair: extract(#list, #i);
#key: extract(#pair, 1);
#value: extract(#pair, 2);
#media (max-width: #value) {
.#{prefix}-#{key} {
#ruleset(); /* invoke the ruleset */
}
}
}
}

Related

Simplifying Repetitive LESS

I am creating a themeing system for a WordPress network that supports multiple layout themes that can support color schemes for a variety of universities. To do so, I periodically compile a LESS file (using lessphp) with school-specific variables and essentially use it as a library of helper classes in the themes.
Each school has 3 colors defined in LESS as: #primary, #secondary and #tertiary. The method is straightforward and functional but requites a lot of repetition in the code. For example:
//Modifier Classes
.primary-lighter-text {
color: lighten(#primary,20);
}
.sec-lighter-text {
color: lighten(#secondary,20);
}
.tert-lighter-text {
color: lighten(#tertiary,20);
}
//Backgrounds
.primary-bg {
background-color: #primary;
}
.sec-bg {
background-color: #secondary;
}
.tert-bg {
background-color: #tertiary;
}
//Borders
.primary-border{
border-color: #primary;
}
.sec-border {
border-color: #secondary;
}
.tert-border {
border-color: #tertiary;
}
Nothing complicated from a LESS standpoint, but if I want to add a new helper class, I have to create 3. Is there a more succinct way to achieve this?
You can simplify it by making use of array loops. All you have to modify in case of a new addition would be to modify the array variables at the end.
.loop-column(#index) when (#index > 0) { /* Recursive Mixin with Guard condition. Mixin is processed only when the condition is satisfied */
.loop-column(#index - 1); /* Call the mixin again with a decremented counter */
#ctype: extract(#type, #index); /* Extract the type value corresponding to the index from the array */
#color: extract(#colors, #index); /* Extract the color value corresponding to the index from the array */
/* Form and Output the necessary classes and properties */
.#{ctype}-lighter-text { /* Selector interpolation to dynamically form the selector */
color: lighten(#color,20);
}
.#{ctype}-bg {
background-color: #color;
}
.#{ctype}-border{
border-color: #color;
}
}
.loop-column(length(#type));
#type: primary, sec, tert; /* The color types array */
#colors:#fff, #777, #000; /* The color value array for each type */
/* If required the colors can be kept as separate variables also. Refer 2nd demo. */
Demo | Demo 2
Update: (Based on comments from Andrew Cafourek and seven-phases-max)
Since LessPHP is outdated, the following line should be added and the length(#type) should be replaced with the actual count.
.loop-column(0) {};
.loop-column(4);

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

Is ~'' really the best way to pass a null parameter to a LESS mixin?

I am creating a series of mixins for LESS css where I use a mixin like this with media queries:
.animal(#color: ~'', #imgWidth: ~'')
{
& when not (#color = ~'')
{
color: #color;
}
& when not (#imageMargin = ~'')
{
img
{
width: #imgWidth;
}
}
}
As you can see the parameters default to ~'' and then I only output the property if a real value is passed in. This allows me to 'inherit' from the previous media query when nothing is passed in :
#media screen and (min-width: 20em)
{
.cat(#color: red, #imgWidth: 3em);
.tiger(#color: blue, #imgWidth: 5em);
}
#media screen and (min-width: 20em)
{
.cat(#imgWidth: 5em);
.tiger(#imgWidth: 7em);
}
The color doesn't need to be re-set for the second media query, only the #imgWidth parameter. So in the output CSS it isn't included.
This works fine, but its very cumbersome. It this really the only way to do optional parameters that should be ignored if not provided. Why does LESS even output parameters when they're empty? Is there a setting to disable this?

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

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');
}

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.