Simplifying Repetitive LESS - 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);

Related

How to use the default variable for only one of a few variables in less

Here is my mixin
.test(#color:black; #width:100px; #height:50px) {
width:#width;
height:#height;
background:#color;
}
Here is where it's called later
.mydiv {.test('use-mixin-color'; 300px; 150px);}
How can I override the size of .mydiv, while using the color defined in the mixin?
Everything I have tried overrides the mixin color.
To Use mixin in LESS, pass those parameter to override mixin default value :
Soluations :
.test(#color:black; #width:100px; #height:50px) {
width : #width;
height : #height;
background : #color;
}
.mydiv {
.test(#width : 300px; #height : 150px);
}
OUTPUT :
.mydiv {
width: 300px;
height: 150px;
background: black;
}
Helpful :)
In addition to the accepted answer. There're multiple methods (actually infinite) but if you want your mixin to be most easy for use you can provide a "specialization" for a specific argument value or number of arguments. Like this for example:
// usage:
.foo {.test(red, 1px, 2px)}
.bar {.test(3px, 4px)}
// impl.:
.test(#color, #width, #height) {
width: #width;
height: #height;
background: #color;
}
.test(#width, #height) { // <- "no color" specialization
.test(black, #width, #height);
}
Demo.
Also think twice before adding default parameter values for a mixin like:
.test(#color: black, #width: 100px, #height: 50px) { ...
People tend to overuse this feature while it's rarely really necessary (and only creates an extra code-noise) except some specific use-cases.
I.e. consider if you actually expect your mixin to be invoked as:
test;
test(blue, 4em);
// etc.
Do you?
It's usually a good idea to start without default parameter values (at least to protect the mixin against accidental misuse), i.e.:
.test(#color, #width, #height) { ...
and add them later only where and when they are necessary.

LESS: how to convert a list of called mixins to a for loop with a unique call

I crated many mixins to generate different kinds of classes for various purposes. Specifically I have to use a unique colorizer set using the standard bootstrap variable name, such as (only an example):
#type-primary: #fff;
#type-success: #f00;
#type-info: #ff0;
#type-default: #000;
#type-warning: #0f0;
#type-danger: #0ff;
Actually I created my mixins in the following form, with a "mother" as prefix to which I attached various suffixes
.text
{
&-primary { .color_text(#type-primary); }
&-success { .color_text(#type-success); }
&-info { .color_text(#type-info); }
&-default { .color_text(#type-default); }
&-warning { .color_text(#type-warning); }
&-danger { .color_text(#type-danger); }
}
After this, I can then create the final called mixin such as (so simple because it's only an example)
.color_text (#color)
{
color:#color;
}
I woud like to automate and optimize .text mixin to avoid many repeated rows, I think with a for loop. How could be possible?
Final results should be (in this case)
.text-primary {
color: #fff;
}
.text-success {
color: #f00;
}
.text-info {
color: #ff0;
}
.text-default {
color: #000;
}
.text-warning {
color: #0f0;
}
.text-danger {
color: #0ff;
}
In PSEUDO-CODE something like this could be ideal
#type-primary: #fff;
#type-success: #f00;
#type-info: #ff0;
.createContextClass("classNamePrefix",{#type-primary,#type-success,#type-info},mixinToBeCalled);
// Another call could be
.createContextClass("otherClassNamePrefix",{#type-primary,#type-success},otherMixinToBeCalled);
where, in relation to my original code, classNamePrefix should be the name of first part of final CSS class, then is passed an array with all kind of suffix that I wish in final CSS code, and mixinToBeCalled is the mixin that creates all css rules for final .text-primary, .text-success, .text-info.
For the moment, following Seven-Phases-Max' suggestion, I improved his solution in the following way

Overriding mixins in LESS

when defining a mixin multiple times in LESS, and later calling that mixin as follows
.background-color() {
background: red;
}
.background-color() {
background: yellow;
}
body {
.background-color;
}
the result will be a combined output from all the defined mixins
body {
background: red; // << output from mixin #1
background: yellow; // << output from mixin #2
}
while when you apply the same scenario in both Sass & Stylus ( using their own syntax of course ), when you call a mixin that is defined multiple times across your stylesheets, Only the last defined one will be executed ( it will override all previously defined mixins ) as follows.
result Sass and Stylus
body {
background: yellow; // << output from mixin #2
}
how can I override a mixin in LESS so that the output will be from the last defined mixin ?
You can not override them, alternatively use a variable to define the 'background-color'. For Less variables the last declared win.
Also read Pattern-matching
In Less all matching mixins are compiled in the source. You can use namespace to prevent name collisions, for instance:
#ns1 {
.background-color() {
background: red;
}
}
#ns2 {
.background-color() {
background: yellow;
}
}
than you can use:
body {
#ns2 > .background-color;
}
Double properties are also not removed to make some browser hacks possible, example:
#myElement {
width: 300px;
width: 500px\9;
}
To find a solution for your use case you should reformulate your question and explain why you have these same named mixins in the first place.

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

LESS condition based on CSS class to set a LESS variable

I need to set a Less variable to match the website's active theme, ie, each theme has a different color.
I'd like to set #themeColor to the right color, based on the HTML's body CSS class that defines the theme.
For example:
body.themeBlue { #themeColor: blue; }
body.themeRed { #themeColor: red; }
This way I'd only need to use the #themeColor variable inside the other Less files.
Can anyone help?
According to this (http://www.lesscss.org/#-scope) it is possible to do something like that, but I can't make it work. what is going on here?
The LESS file cannot read the actual class applied to the html body element at run time (you would probably need to implement a javascript solution to do something like that).
If you just want to have all themed css ready for use based on the body class, the best way to implement this to have all the necessary theme based css in a mixin, then apply it under the theme classes. This reduces code duplication. For example:
LESS
//mixin with all css that depends on your color
.mainThemeDependentCss() {
#contrast: lighten(#themeColor, 20%);
h1 {color: #themeColor;}
p {background-color: #contrast;}
}
//use the mixin in the themes
body.themeBlue {
#themeColor: blue;
.mainThemeDependentCss();
}
body.themeRed {
#themeColor: red;
.mainThemeDependentCss();
}
CSS Output
body.themeBlue h1 {
color: #0000ff;
}
body.themeBlue p {
background-color: #6666ff;
}
body.themeRed h1 {
color: #ff0000;
}
body.themeRed p {
background-color: #ff6666;
}
For some other answers that deal with aspects or ways of theming, see:
LESS CSS - Change variable value for theme colors depending on body class
LESS.css variable depending on class
LESS CSS: abusing the & Operator when nesting?
Variables in Less are actually constants and will only be defined once.
Scope works within its code braces, so you would need to nest your CSS within each theme you want (which means duplication).
This is not ideal as you would need to do this:
body.themeBlue {
#color: blue;
/* your css here */
}
body.themeRed {
#color: red;
/* your css here AGAIN :( */
}
You could, however, try to use variables like this:
#color: black;
#colorRed: red;
#colorBlue: blue;
h1 {
color: #color; // black
body.themeRed & {
color: #colorRed; // red
}
body.themeBlue & {
color: #colorBlue; // blue
}
}
You would only need to declare the colours once, but you would need to constantly do the "body.themeRed" etc. prefixes where the colour varies depending on the theme.
You could actually use #import to load your theme! So common.less would contain all your default styles and #themeColor will be applied to it.
.mainThemeDependentCss() {
//file with all themed styles
#import 'common.less';
}
//use the mixin in the themes
body.themeBlue {
#themeColor: blue;
.mainThemeDependentCss();
}
body.themeRed {
#themeColor: red;
.mainThemeDependentCss();
}
BTW you should avoid using body selector in your common.less, because it wouldn't work.