Build List of Selectors with Less

Build list of selectors with LESS

As already mentioned your attempt is almost there, it does not work because of variable visibility rules.
Notice that each .selector-list iteration defines new @selector-list variable which have higher precedence in the current scope but does not override @selector-list variables for the outer scope (i.e. scopes of the previous .selector-list iterations and above). So when you use @selector-list after the initial .selector-list call you get the value set in the "highest"
.selector-list iteration (i.e. the first one with @i = 1).

To "return" a value from the last iteration of a recursive loop you need to define a variable with this value only within that last iteration. Usually the simplest way to do this is to provide a "terminal" mixin (i.e. a specialization for the last call of the recursive mixin). In fact you would need such terminal anyway to handle the final comma of the list. E.g.:

#1

.selector-list(@parent, @children, @i: 1, @list: "") when (@i < length(@children)) {
@child: extract(@children, @i);
.selector-list(@parent, @children, (@i + 1), "@{list} @{parent} @{child},");
}

.selector-list(@parent, @children, @i, @list) when (@i = length(@children)) {
@child: extract(@children, @i);
@selector-list: e("@{list} @{parent} @{child}");
}

// usage:

@text-elements: p, ul, ol, table;

.selector-list("body.single .entry-content", @text-elements);
@{selector-list} {
line-height: 1.8;
}

-

#2 Same as above just slightly "optimized":

.selector-list(@parent, @children, @i: length(@children), @list...) when (@i > 1) {
.selector-list(@parent, @children, (@i - 1),
e(", @{parent}") extract(@children, @i) @list);
}

.selector-list(@parent, @children, 1, @list) {
@selector-list: e(@parent) extract(@children, 1) @list;
}

// usage:

@text-elements: p, ul, ol, table;

.selector-list("body.single .entry-content", @text-elements);
@{selector-list} {
line-height: 1.9;
}

-

#3 Speaking of the use-case in general, a "string-based selector manipulation" is not always a good idea in context of Less. The main problem is that Less does not treat such strings as "native" selectors and most of advanced Less features won't work with them (e.g. Less won't recognize ,, & and similar elements there so such rules can't be nested, extend also can't see such selectors etc. etc.).
An alternative, more "Less-friendly" approach is to define such list as a mixin rather than a variable, e.g.:

.text-elements(@-) {p, ul, ol, table {@-();}}

body.single .entry-content {
.text-elements({
line-height: 1.8;
});
}

and (when you also need to reuse body.single .entry-content /.text-elements/):

.text-elements(@-) {
p, ul, ol, table
{@-();}}

.selector-list(@-) {
body.single .entry-content {
.text-elements(@-);
}
}

.selector-list({
line-height: 1.9;
});

etc.

-

P.S. Also, speaking in even more general, don't miss that in Less a media query may be put into selector ruleset, so depending on a use-case it's also often more easy to write a list of selectors once and set media depended styles inside it (i.e. doing it in opposite to the standard CSS method where you have to repeat same selector(s) for each media query).

Generating selector lists in LESS

In Less 1.4.0+ you can :extend a placeholder class:

.dp-list-item {
display: list-item;
}
.label-color {
color: red;
}

// This function generates selectors and corresponding css
.gen (@tag) {
@sel: ~"#@{tag}:checked ~ ul > li";
@{sel} {
&[data-index~=@{tag}]:extend(.dp-list-item){}
& > label[for=@{tag}]:extend(.label-color){}
}
}

// Generate required selectors and css
.gen("foo");
.gen("bar");
.gen("baz");

Forked pen

Generated CSS:

.dp-list-item,
#foo:checked ~ ul > li[data-index~="foo"],
#bar:checked ~ ul > li[data-index~="bar"],
#baz:checked ~ ul > li[data-index~="baz"] {
display: list-item;
}
.label-color,
#foo:checked ~ ul > li > label[for="foo"],
#bar:checked ~ ul > li > label[for="bar"],
#baz:checked ~ ul > li > label[for="baz"] {
color: red;
}

As you can see, the only drawback is that both classes which I've used as placeholders will be in the generated CSS. I believe this can't be worked around until Less implements something akin to Sass' placeholder selectors.

ps. I've omitted the global li rules which are not part of the mixin for brevity.


As per OP request, here is a Sass (with .scss syntax) equivalent:

//placeholder selectors
%dp-list-item {
display: list-item;
}
%label-color {
color: red;
}

// This function generates selectors and corresponding css
@mixin gen($tag) {
##{$tag}:checked ~ ul > li {
&[data-index~=#{$tag}] {
@extend %dp-list-item;
}
& > label[for=#{$tag}] {
@extend %label-color;
}
}
}

// Generate required selectors and css
@each $item in (foo bar baz) {
@include gen($item);
}

Demo

Generated CSS:

#foo:checked ~ ul > li[data-index~=foo], #bar:checked ~ ul > li[data-index~=bar], #baz:checked ~ ul > li[data-index~=baz] {
display: list-item;
}

#foo:checked ~ ul > li > label[for=foo], #bar:checked ~ ul > li > label[for=bar], #baz:checked ~ ul > li > label[for=baz] {
color: red;
}

You can see that Sass' syntax is rather verbose when compared to Less. Sass also has some nice features such as control directives and excellent interpolation which I've applied in the example above.

multiple nested selectors with variables in Less

#1
Just yet one more solution in addition to @helderdarocha's answer and those given in https://stackoverflow.com/a/23954580/2712740. Maybe be this one could look a bit more clear:

// define header list as usual just
// put a mixin call with some predefined name there
h1, h2, h3, h4, h5, h6 {.headings}

// now to add styles/childs to the header list just add the mixin definitions:

.headings() {
some: rule;
}

.headings() {
a {color: inherit}
}

.headings() {
span {another: rule}
}

// etc.

The limitation of this solution is that h1, h2, h3 ... {} and .headings should be defined at the same level. Additionally, it's important to keep in mind that all these styles will output to CSS at the point of h1, h2, h3 ... {} definition not at the point of .headings definitions, so it may break your cascading overrides if you have some).


#2
The alt. solution I'm copy-pasting from https://stackoverflow.com/a/23954580/2712740 #3, basicaly it's the same as #1 but w/o its limitations (just having more special scary symbols):

// the "variable":
.headings(@-) {
h1, h2, h3, h4, h5, h6
{@-();}}


// usage:

.headings({
some: rule;
});

.headings({
a {color: inherit}
});

.headings({
span {another: rule}
});

//etc.

How to use begin with selector in Less

It is not working because your syntax seems to be wrong and not because of any issues with Less.

The below code is invalid because of the . present between the label and the class^="label-"]. Attribute selectors do not require a . before them. It is necessary only for class selectors.

.label.[class^="label-"]{
background-color: rgba(0,37,100,0.4);
}

The correct version would be the following:

.label[class^="label-"]{
background-color: rgba(0,37,100,0.4);
}

and so in Less terms, if you want nesting, it would be as follows:

.label{ 
&[class^='label-']{
background-color: rgba(0,37,100,0.4);
}
}

.label.[class^="label-"] {  /* this won't work */  background-color: rgba(0, 37, 100, 0.4);}
.label[class^="label-"] { /* this will */ color: green;}
<label class='label-a label'>Label A</label><label class='label-b label'>Label B</label>

Add a value to a list in Less CSS

You can not do that. Less variables use the last declaration wins rule. A variable referencing itself will create a loop, the assigned value becomes the last declared, and so on.

You can use a loop to build a list which you can assign as a value to a property.

The LESS loops used to generate column classes in twitter - How do they work? shows you how to build a list of selectors.

Build a repetitive selector within a Less loop

I would go about it somehow in this manner:

.generateClasses (@index, @n, @in:"") when (@index > 0) {
@concatenate: "@{in} .repeatedClass";
@selector: ~".staticClass @{concatenate} > .finalStaticClass";
@{selector}{ height: unit(@n,px) };
.generateClasses((@index - 1), (unit(@n) + 10), @concatenate);
}
.generateClasses(0, @n, @in){};

.generateClasses(4, 10px);

Where you pass on to the next loop the concatenated generated classes and each time add another class. The @index is the counter for the loop, and @n is the value that you want to increase.

CSS output:

.staticClass  .repeatedClass > .finalStaticClass {
height: 10px;
}
.staticClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 20px;
}
.staticClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 30px;
}
.staticClass .repeatedClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 40px;
}

Edit - for older versions of Less:

in Less <= 1.3.3, you need to include the individual concatenating loops in a separate role (it is called .test in the example below), that confines the variable. Then you can loop through this, doing something along these lines:

.generateClasses (@index, @n, @in:"") when (@index > 0) {
@concatenate: "@{in} .repeatedClass";
@selector: ~".staticClass @{concatenate} > .finalStaticClass";
.generateClasses((@index - 1), (unit(@n) + 10), @concatenate);
}
.generateClasses(0, @n, @in){};

.test(@i, @ni){
.generateClasses(@i,@ni);
@{selector} {
height: @ni;
}
}

.printClasses(@i:1,@ni:10px) when (@i > 0) {
.test(@i,@ni*@i);
.printClasses(@i - 1,@ni);
}

.printClasses(4);

output CSS will now be:

.staticClass  .repeatedClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 40px;
}
.staticClass .repeatedClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 30px;
}
.staticClass .repeatedClass .repeatedClass > .finalStaticClass {
height: 20px;
}
.staticClass .repeatedClass > .finalStaticClass {
height: 10px;
}

if you just need to generate a selector once at a time, you can skip the second loop and just call the .test() mixin wherever you need it.

Less: Combining selector variables

(almost copy-pasting from the more wide https://stackoverflow.com/a/23954580/271274)

There're two problems with your attempt:

  1. By definition a content of escaped strings is not a subject for any kind of evaluation at all, so commas (as well as any other special ops) have no meaning there.
  2. Variable interpolation in selectors assumes a single interpolated variable contains only a single selector element. So, strictly speaking, even ~"h1.someclass > a" is already nothing but a hack expected to have side-effects and unspecified/undefined behaviour for anything but extremely trivial cases.

So in your code above the value of @titles works just as a simple/single selector element (the same as body for example).

I.e. in summary and in general, "string-based selector manipulation" (like ~"@{title1}, @{title2}") should be avoided where possible simply because in Less selectors are not strings and strings are not selectors (nor they automatically converted to each other except in, yet again, certain extremely trivial cases).


So far the only non-hackish method to define a reusable list of selectors in Less is a mixin (mixins can be considered as "variables" too even if they have another syntax) that puts an arbitrary set of rules into a ruleset having the said list as its selector. E.g. for your example above it would be something like:

@title1: ~"h1.someclass  > a";
@title2: ~"h1.otherclass > a";

.titles(@rules) {
@{title1}, @{title2} {@rules();}
}

// usage:

.titles({
&:after {
display: none;
}
});

Demo.

LESS: Variables that contains multiple selector for use inside another selector

See also: https://github.com/less/less.js/issues/2263

If you are enable to split @var into two (or more) separated variables you can use the following Less code:

@var1: ~"> a";
@var2: ~"> a:hover";
body > header {@{var1},@{var2} { > strong > em {color:red;}}}

The preceding will compile into the following CSS code:

body > header > a > strong > em,
body > header > a:hover > strong > em {
color: red;
}

When @var: "a", "a:hover"; you can also use:

@var1: e(extract(@var,1));
@var2: e(extract(@var,2));

body > header {
> @{var1}, > @{var2} {
> strong > em {
color: red;
}
}
}

Or use a complex mixin (as Bootstrap does, see: LESS loops used to generate column classes in twitter - How do they work?) to build your selectors:

.mixin(@iterator: 0; @selectors: ~""; @seperator: ~"") when (@iterator < length(@var)) {
@blah: ~"body > header > @{selector} > strong > em";
@selector: extract(@var,@iterator + 1);
@selectorlist: ~"@{selectors} @{seperator} @{blah}";
.mixin((@iterator + 1); @selectorlist; ~",");
}
.mixin(@iterator; @selectors: ~""; @seperator: ~"") when (@iterator = length(@var)) {
@{selectors} {
color:red;
}
}
.mixin();


Related Topics



Leave a reply



Submit