Playing with LESS - optimize css generated by passing ruleset to mixin - css

I'm just playing with LESS lately. I wanted to generate rules for elements with numeric ID. At some stage I got code like this:
#myRule: {padding: 0;};
.myLoop(#c, #rules) when (#c >= 0) {
.myLoop((#c - 1), #rules);
&[id*=#{c}] { #rules(); }
&[name*=#{c}] { #rules(); }
}
.myClass {
.myLoop(2, #myRule);
}
which compiles to
.myClass[id*=0] {
padding: 0;
}
.myClass[name*=0] {
padding: 0;
}
.myClass[id*=1] {
padding: 0;
}
.myClass[name*=1] {
padding: 0;
}
.myClass[id*=2] {
padding: 0;
}
.myClass[name*=2] {
padding: 0;
}
My question is: can I in any way make it compile to sth like this:
.myClass[id*=0],
.myClass[name*=0],
.myClass[id*=1],
.myClass[name*=1],
.myClass[id*=2],
.myClass[name*=2] {
padding: 0;
}
I was looking for something like 'extending mixins`, 'parametric extend' or 'extending ruleset' but all lead to issues that are either 'wontfix' or 'nice-to-have' :-) So I guess it's not yet possible, but I would just like to reach out to people more familiar with less then I am, to be sure.

Yes, neither extending parametric mixins nor scoped extend are possible currently, so the easiest method to achieve the result is to extend a dummy ruleset. E.g.:
.my-repeat(#i, #f) when (#i >= 0) {
.my-repeat((#i - 1), #f);
&[id*=#{i}], &[name*=#{i}] {#f();}
}
.my-class-style {
padding: 0;
}
.my-class {
.my-repeat(2, {
&:extend(.my-class-style);
});
}
where .my-class-style is the dummy selector to appear in the resulting CSS too.

Related

How can I use a loop in LESS to create specific class names for typography?

I want to generate 9 typography classes, each with the following:
font-size: 2rem;
line-height: 1rem;
I'll be using standard typographic multipliers for font sizes and line-height. Instead of hard-coding all of these CSS classes, I was wondering if there was a more elegant way of generating them in a loop using LESS.
I found the following from another thread:
#iterations: 5;
.span-loop (#i) when (#i > 0) {
.span-#{i} {
width: ~"#{i}%";
}
.span-loop(#i - 1);
}
.span-loop (#iterations);
Which generates:
.span-5 {
width: 5%;
}
.span-4 {
width: 4%;
}
.span-3 {
width: 3%;
}
.span-2 {
width: 2%;
}
.span-1 {
width: 1%;
}
This is pretty close, but I'd love for my class names to more "named". How can I use a loop to generate classes for:
.small { }
.caption { }
.body { }
.subheader { }
.title { }
.headline { }
etc...
I'm also not tied to LESS, so if there's a better CSS preprocessor language, then I'm happy to use that instead :)
Thank you!
An example from documentation for further modification;)
for more complicated code, it is better to use scss than less
.for(#list, #code) {
& {
.loop(#i: 1) when (#i =< length(#list)) {
#value: extract(#list, #i);
#code();
.loop(#i + 1);
}
.loop();
}
}
#elements: small, caption, body, subheader, title, headline;
.for(#elements, {
#remfont: #i+1;
#remline: ((#i+1) * 1.5 / 3);
.#{value} {
font-size: ~"#{remfont}rem";
line-height: ~"#{remline}rem";
}
});

sass prepend root class to current selector

I am having trouble prepending the root class to my current selector in sass. I have the following code:
.cta-two-columns {
&__text-holder {
#at-root&#{__inner} {
// also tried #at-root __inner&{#}
// and many others like #at-root__inner
padding: rem(25px);
}
}
}
but this gives me the following:
.cta-two-columns__text-holder__inner {
padding: rem(25px);
}
I don't understand the above - what's the point of at-root as you may as well just do &__inneras it gave me the same as the two things I have shown I tried
how do I get
.cta-two-columns__text-holder .cta-two-columns__inner {
}
without having to resort to
.cta-two-columns {
&__text-holder {
.cta-two-columns__inner {
padding: rem(25px);
}
}
}
Or is this the only way to do it in sass?
#at-root doesn't really work like you may think in this case. #at-root will simply make the declaration outside the nest. To better understand, add another CSS declaration like below:
.cta-two-columns {
&__text-holder {
margin: 10px;
#at-root&#{__inner} {
padding: rem(25px);
}
}
}
This will produce the following CSS code:
.cta-two-columns__text-holder {
margin: 10px;
}
.cta-two-columns__text-holder__inner {
padding: rem(25px);
}
Simply imagine how the selector will be created without #at-root then make it outside.
Without it will produce this:
.cta-two-columns__text-holder {
margin: 10px;
}
.cta-two-columns__text-holder .cta-two-columns__text-holder__inner {
padding: rem(25px);
}
Then we simply omit .cta-two-columns__text-holder.
One idea to obtain what you want is to consider a variable where you can declare the main class then you will be able to nest as many element as you want:
$sel: '.cta-two-columns';
#{$sel}__text-holder {
#{$sel}__inner {
padding: rem(25px);
}
}
Will produce:
.cta-two-columns__text-holder .cta-two-columns__inner {
padding: rem(25px);
}
with more nested elements:
$sel: '.cta-two-columns';
#{$sel}__text-holder {
#{$sel}__outer {
#{$sel}__inner{
#{$sel}__wrap{
padding: rem(25px);
}
}
}
}
Will produce
.cta-two-columns__text-holder .cta-two-columns__outer .cta-two-columns__inner .cta-two-columns__wrap {
padding: rem(25px);
}

Can I add a variable to a mixin function name in Less?

Is this possible to do in some way I don't know? I'm trying to add a variable in the mixin function name.
#mybar: Test;
.mymixin() {
padding: 10px;
}
.mymixin#{mybar}() {
padding: 10px;
}
.test {
.mymixinTest();
}
I think it should work if you do:
.myMixin(#myvar) {
.myMixin#{myvar} {
//your css
}
}

Using SASS's #for for multiple selectors and 1 body [duplicate]

I'm working with the SCSS syntax of SASS to create a dynamic grid system but I've hit a snag.
I'm trying to make the grid system completely dynamic like this:
$columns: 12;
then I create the columns like this:
#mixin col-x {
#for $i from 1 through $columns {
.col-#{$i} { width: $column-size * $i; }
}
}
Which outputs:
.col-1 {
width: 4.16667%;
}
.col-2 {
width: 8.33333%;
}
etc...
This works well but what I want to do next is dynamically generate a long list of column classes separated by commas based on the number of $columns chosen - e.g I want it to look like this:
.col-1,
.col-2,
.col-3,
.col-4,
etc... {
float: left;
}
I've tired this:
#mixin col-x-list {
#for $i from 1 through $columns - 1 {
.col-#{$i}-m { float: left; }
}
}
but the output is this:
.col-1 {
float: left;
}
.col-2 {
float: left;
}
etc...
I'm a little stuck on the logic here as well as the SCSS syntax required to create something like this.
Does anyone have any ideas?
I think you may want to take a look at #extend. If you set that up something like:
$columns: 12;
%float-styles {
float: left;
}
#mixin col-x-list {
#for $i from 1 through $columns {
.col-#{$i}-m { #extend %float-styles; }
}
}
#include col-x-list;
It should render in your css file as:
.col-1-m, .col-2-m, .col-3-m, .col-4-m, .col-5-m, .col-6-m, .col-7-m, .col-8-m, .col-9-m, .col-10-m, .col-11-m, .col-12-m {
float: left;
}
#extend in the docs.
There's also a way to do what your question is specifically asking for: generate (and use) a list of classes with commas separating them. D.Alexander's response totally works in your situation, but I'm posting this alternative in case there's another use case for someone looking at this question.
Here's a Pen demonstrating: http://codepen.io/davidtheclark/pen/cvrxq
Basically, you can use Sass functions to achieve what you want. Specifically, I'm using append to add classes to my list, separated by commas, and unquote to avoid compilation conflicts with the period in the classnames.
So my mixin ends up looking like this:
#mixin col-x {
$col-list: null;
#for $i from 1 through $columns {
.col-#{$i} {
width: $column-size * $i;
}
$col-list: append($col-list, unquote(".col-#{$i}"), comma);
}
#{$col-list} {
float: left;
}
}
thnx to #davidtheclark here is a more generic version:
#mixin attr-x($attr, $attr-count: 10, $attr-steps: 10, $unit: '%') {
$attr-list: null;
#for $i from 1 through $attr-count {
$attr-value: $attr-steps * $i;
.#{$attr}#{$attr-value} {
#{$attr}: #{$attr-value}#{$unit};
}
$attr-list: append($attr-list, unquote(".#{$attr}-#{$attr-value}"), comma);
}
#{$attr-list} {
//append style to all classes
}
}
Use it like this:
#include attr-x('margin-left', 6, 5, 'px');
//or
#include attr-x('width');
The result looks like this:
.margin-left5 {
margin-left: 5px; }
.margin-left10 {
margin-left: 10px; }
...
.margin-left30 {
margin-left: 30px; }
.width10 {
width: 10%; }
.width20 {
width: 20%; }
...
.width100 {
width: 100%; }

How do I create nested loops with LESS CSS?

What I know is that this:
#iterations: 8;
.mixin-loop (#index) when (#index > 0) {
.my-class-#{index} {
width: (100% / #index);
}
.mixin-loop(#index - 1);
}
.mixin-loop (0) {}
.mixin-loop(#iterations);
… Will result in this:
.my-class-8{width:12.5%}
.my-class-7{width:14.285714285714286%}
.my-class-6{width:16.666666666666668%}
.my-class-5{width:20%}
.my-class-4{width:25%}
.my-class-3{width:33.333333333333336%}
.my-class-2{width:50%}
.my-class-1{width:100%}
… Making it the LESS equivalent of:
for (var i = 8; i > 0; -- i) {
// …
}
My question is: What would the LESS equivalent of:
for (var i = 8; i > 0; -- i) {
for (var j = 4; j > 0; -- j) {
// …
}
}
… Look like?
Hm, nevermind—Found it myself.
I’m leaving the answer here for posterity’s sake:
#maxi: 8;
.i-loop (#i) when (#i > 0) {
#maxj: 8;
.j-loop (#j) when (#j > 0) {
.my-class-#{i}-#{j} {
width: (100% / #i);
height: (100% / #j);
}
.j-loop(#j - 1);
}
.j-loop (0) {}
.j-loop(#maxj);
.i-loop(#i - 1);
}
.i-loop (0) {}
.i-loop(#maxi);
An Unnested Solution
I'm only offering this as an alternative here for final output code purposes. My answer does not really address nesting of loops directly (as your question is and your own answer found that solution). Rather, it challenges whether nesting is even best to solve the problem you faced.
Assuming a class structure just as you have (say my-class-2-6 for example), you can reduce from 64 output CSS selectors to just 16 by not nesting them and instead using CSS3 attribute selectors (which may not be desirable, depending on target browsers you need to support). Thus this LESS:
#maxi: 8;
#maxj: 8;
#iSelStart: ~'[class^=my-class-';
#jSelStart: ~'[class$=-';
#ijSelEnd: ~']';
.i-loop (#i) when (#i > 0) {
#{iSelStart}#{i}#{ijSelEnd} {
width: (100% / #i);
}
.i-loop(#i - 1);
}
.j-loop (#j) when (#j > 0) {
#{jSelStart}#{j}#{ijSelEnd} {
height: (100% / #j);
}
.j-loop(#j - 1);
}
//stop loops
.i-loop (0) {}
.j-loop (0) {}
//itialize loops
.j-loop(#maxj);
.i-loop(#maxi);
Becomes this CSS:
[class$=-8] {
height: 12.5%;
}
[class$=-7] {
height: 14.285714285714286%;
}
[class$=-6] {
height: 16.666666666666668%;
}
[class$=-5] {
height: 20%;
}
[class$=-4] {
height: 25%;
}
[class$=-3] {
height: 33.333333333333336%;
}
[class$=-2] {
height: 50%;
}
[class$=-1] {
height: 100%;
}
[class^=my-class-8] {
width: 12.5%;
}
[class^=my-class-7] {
width: 14.285714285714286%;
}
[class^=my-class-6] {
width: 16.666666666666668%;
}
[class^=my-class-5] {
width: 20%;
}
[class^=my-class-4] {
width: 25%;
}
[class^=my-class-3] {
width: 33.333333333333336%;
}
[class^=my-class-2] {
width: 50%;
}
[class^=my-class-1] {
width: 100%;
}
So the example of my-class-2-6 would target the start of the class name my-class-2 giving a width: 50% and target the end of the class name -6 which would give a height: 16.666666666666668%;.
Just a thought for any future users facing a similar situation who are only worried about targeting CSS3 browsers.
Update: Added Protection to not Incorrectly Target
As an after thought, it occurred to me that if you have various types of classes that may have an ending of -1 or -2 etc., then your ending CSS may need to have an additional set of code to help filter for just that class. So the j loop code above would need to have a change to the selector string like so:
#{iSelStart}#{ijSelEnd}#{jSelStart}#{j}#{ijSelEnd} { /*properties*/}
Which would then output this format of code:
[class^=my-class-][class$=-1] {
/*properties*/
}
This way it is looking specifically for the my-class- "class" that ends in -1, and would ignore selecting another class like another-class-1 as the original code above would still select. Whether this is an issue or not would purely be related to the design and class naming used in one's site.
Old question but maybe it's worth mentioning that Less can now do this easier
Function
.for(#i, #n, #r){#r();._(#i)}
.for(#n, #r)when(isnumber(#n)){.for(0, #n, #r)}
.for(#i, #n, #r)when not(#i = #n - 1){.for((#i + ((#n - #i) / abs(#n - #i))), #n, #r)}
Usage
.for(3, {._(#i) {
.for(3, {._(#j) {
item-#{i}-#{j} {
i: #i;
j: #j;
}
}});
}});
Example: Codepen
.loop(#n: 1, #m: #n, #k: #n * #m) when(#k > 0) {
.loop(#n, #m, #k - 1);
#i: `Math.floor((#{k} - 1) / #{m})`;
#j: #k - #i * #n - 1;
/*
#i runs up 1 to #n,
#j runs up 1 to #m and
#k runs up 1 to #n * #m
for example:
*/
&:nth-child(#{k}) {
top: 50px * #i;
left: 100px * #j;
}
}
/* using: */
.loop(3,4);

Resources