Dynamic column based on class names in LESS - less

I'm trying to do something like this
.col-#{index} {
width: #col-n-width;
left: {index - 1 } * #col-n-width;
}
But not sure how to access the {index} variable inside. Is that even possible?

The #{} syntax is used only when you need to interpolate variables in property names or selectors or URLs etc. It is not required when all you need to do is plain math operations. Just reference it as #index like in the below sample.
.col-#{index} {
width: #col-n-width;
left: (#index - 1) * #col-n-width;
}
For example if you are trying to write a loop/mixin, the code would be something like the below:
#col-n-width: 200px;
.create-cols(#index) when (#index > 0) {
.col-#{index} {
width: #col-n-width;
left: (#index - 1) * #col-n-width;
}
.create-cols(#index - 1);
}
.create-cols(5);
Note: The #index must be a variable defined in your Less file. Less compiler cannot read the HTML and dynamically create classes based on it. In the worst case, you'd at least have to use client-side compilation through JS using less.modifyVars() but client-side compilation of code is generally not recommended.

Related

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

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 */
}
}
}
}

How should I reset a less variable using its own value

I want to create a rgba from a color less variable and assign the result to the same variable, but I can't do this by the following code.
#navbar-default-bg: rgba(red(#navbar-default-bg), green(#navbar-default-bg), blue(#navbar-default-bg), 0.9);
The above code would result in the following error being thrown on compilation:
NameError: Recursive variable definition for #navbar-default-bg
Recursive variable definitions won't work in Less because of the way Less does lazy loading of the variables. This would mean that the last definition for that variable (within the same scope) will be the one that is used. In your example it would result in an error because the variable cannot reference itself (to get its originally declared value).
Quoting Less Website:
When defining a variable twice, the last definition of the variable is used, searching from the current scope upwards. This is similar to css itself where the last property inside a definition is used to determine the value.
The best way to create a rgba color value from a given rgb color is to use the built-in fade function (like shown below). But note that, the value cannot be assigned back to the same variable.
#navbar-default-bg: rgb(255, 0, 0);
#sample{
color: fade(#navbar-default-bg, 90%);
}
The above Less code when compiled would produce the following CSS output:
#sample {
color: rgba(255, 0, 0, 0.9);
}
Of-course, you could do something like mentioned in this answer to sort of achieve a reset effect but my personal opinion is that it is way too much complexity and effort for something that can probably be achieved in a different way.
Here is a sample implementation of the method mentioned in that answer adapted to suit this question. (Code is added below just in-case the link becomes inactive.)
.init() {
.inc-impl(rgb(255, 0, 0), 0.1); // set initial value
}
.init();
.inc-impl(#new, #i) {
.redefine() {
#color: #new;
#alpha: #i;
}
}
.someSelector(#name) {
.redefine(); // this sets the value of counter for this call only
.inc-impl(rgba(red(#color), green(#color), blue(#color), #alpha), (#alpha + 0.1)); // this sets the value of counter for the next call
#className: ~"#{name}";
.#{className}
{
color: #color;
}
}
.someSelector("nameOfClass");
.someSelector("nameOfClass1");
.someSelector("nameOfClass2");

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

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.