2

Would it be possible in LESS to have a mixin nested inside another one so that the former gets called only when the element is child of an element with the latter mixin?

I know, confusing, here is a simple example (not working code, just concept):

LESS

.foo(@x) {
    width: @x;

    .foo(@y) {
        width: @y/@x;
    }
}

.a {
    .foo(20px);

    .b { 
        .foo(2);
    }
}

Output CSS

.a {
    width: 20px;
}

.a .b {
    width: 10px;
}

When I do this, calling .foo(2) on .b gives compiles to width: 2.

Is this supposed to be like this by design, or am I getting something wrong in the syntax? Also, am I approaching the problem from a completely wrong angle and there is perhaps a much simpler solution that I am not considering?

EDIT

Ok, apparently that was fixed with the newest versions of LESS, what I am trying to achieve, though, is slightly more complicated than the minimal example I gave above.

Basically what I would like to happen is that every .foo which is a child of another element with the .foo mixin would take its parent variable for calculation, so, ideally

LESS

.foo(@x) {
    width: @x;

    .foo(@y) {
        width: (@x/@y);
    }
}

.a {
    .foo(100px);

    .b { 
        .foo(2px);

        .c {
            .foo(5px);
            /* ...and so on */
        }
    }
}

Output CSS

.a {
    width: 100px;
}

.a .b {
    width: 50px;
}

.a .b .c {
    width: 10px;
}

What I get is, instead:

.a .b .c {
    width: 50px;
}

I tried to modify the LESS as follows:

.foo(@x) {
    width: @x;

    .foo(@y) {
        @x:    (@x/@y)
        width: (@x);
    }
}

But I get a syntax error for recursive variable definition. Apparently LESS doesn't allow for definitions like:

@a: 1;
@a: (@a+1);
Community
  • 1
  • 1
Sunyatasattva
  • 5,619
  • 3
  • 27
  • 37

1 Answers1

5

I think the main problem here would be the @y/@x. Try @x/@y and it should give something more expected ;-)

As the nested mixins get interpreted different throughout different less implementations I will now split the answer in two parts:

1. Sollution that worked on less2css.org with LESS >1.3.1 (using your nested mixins)

Otherwise I think the above code actually does what you want on less2css.org. Just as a notion in LESS 1.4, you need to be careful with the math as by default it needs to be in brackets.

If you now just call such a mixin on nonnested rules, like

.b { .foo(@y); }

you neen to use units in the input variable, or ad unit() into your mixin, otherwise you will only get the number you put in, 2 for example:

.foo(@x) {
    width: unit(@x,px);
    .foo(@y) {
        width: (@x/@y);
    }
}

.a {
    .foo(20px);
    .b{
        .foo(2);
     }
}
.c {
    .foo(2);
}

will output CSS:

.a {
  width: 20px;
}
.a .b {
  width: 10px;
}
.c {
  width: 2px;
}

You could get even fancier, and check with guards if the attribute in the subclass has pixles as a unit, so that you can also nest classes where you don't pass a factor:

.foo(@x) {
    width: @x;
    .foo(@y) when (ispixel(@y)) {
        width: @y;
    }
    .foo(@y) when not (ispixel(@y)) {
        width: (@x/@y);
    }
}

However testing this nested mixin solution appears to work only on less2css.org, but not on jsbin.com, lesstester.com and some other services [where you need to call the nested (second) level of mixin with .foo .too, to apply the second level of styling from the mixin].

So I propose an alternative approach, that I tested and it seems to work on all mentioned pages using less-css compilers with less >1.2.

2. Solution using guards with ispixel that should work on most LESS >1.2 installations

Instead of your nested mixin you could build two mixins, that are based on guards checking for pixels as the unit.

  • if attribute @x is in pixels => return width:@x; and assign @x to variable @width
  • if attribute @x is not in pixels => return width:@width/@x; (note: @width needs to be previously assigned by calling the mixin with the @x in px first)

example LESS:

.foo(@x) when (ispixel(@x)) {
        width:@x;
        @width:@x;
}
.foo(@x) when not (ispixel(@x)) {
        width: (@width/@x);
}


.a, .b {
  background-color: #ddd;
  height: 100px;
}
.a {
  .foo(100px);
  .b {
    background-color: red;
    .foo(2);
  }
}

th output CSS:

.a, .b {
  background-color: #ddd;
  height: 100px;
}
.a {
  width: 100px;
}
.a .b {
  background-color: red;
  width: 50px;
}

Differs from your approach but is perhaps a bit more straight forward, and seems to work well.

Edit:

So as you don't want to distinguish between input with unit and input without unit, I can only think of calling a two parametric mixin, where one parameter is used for the base (width in your case) and the second as a factor that defaults to 1. And this you can call now recursively as many times as you want.

LESS:

.foo(@x,@y:1){
    width:unit(@parent,px);
    @parent:(@x/@y);
}


.a {
    .foo(100px);
    .b{
        .foo(@parent,2px);
        .c{
            .foo(@parent,5px);
            .d{
                .foo(@parent,0.05);
            }
        }
    }
}

output CSS:

.a {
  width: 100px;
}
.a .b {
  width: 50px;
}
.a .b .c {
  width: 10px;
}
.a .b .c .d {
  width: 200px;
}

So now it does not matter what unit the input has because you can assign the desired unit for the output in the mixin. When you call the mixin it overwrites the @parent variable for the current scope, which gets then inherited to nested rules, where you can use it as the width parameter @x when calling the mixin again. This should give you the desired result.

Martin Turjak
  • 20,896
  • 5
  • 56
  • 76
  • Ahah, I am sorry for the silly mathematical mistake, anyhow, my problem is that doesn't seem to work. As you can see in this little [JSBin](http://jsbin.com/ugoyom/1) it is not outputting the expected CSS on the nested `.b` element. But, rather, it is outputting the first `.foo(@x)` mixin. Calling [`.foo .foo(2px)` actually works](http://jsbin.com/ugoyom/2) (albeit not consistently I must say). But I don't think it makes sense in my case to use this syntax (my case is a bit more complex than the one described above) – Sunyatasattva Apr 14 '13 at 21:18
  • Thank you! Actually the problem was that I was using LESS 1.3.0. Unfortunately I can't approach the problem with your second solution, because my real case problem doesn't use a pixel definition in the parent and an absolute definition in the child, but both absolute definitions. Now the problem is, is it possible to have the recursion happen indefinitely? (See my edit for more details on what I mean, I upvoted the answer but not accepted it because my problems still stands unfortunately) – Sunyatasattva Apr 15 '13 at 14:24
  • The nested solution I have given above can be nested in as manny leves deep as desired and it works perfectly in newer versions of LESS, and I am not sure why you don't want to distinguish between input with unit and input without unit. However, a way to solve this in 1.3.0 would be to use a mixin that takes the parents parameter as an additional argument. – Martin Turjak Apr 17 '13 at 10:03
  • I am now using the latest LESS, no need to support 1.3.0. Perhaps I didn't explain myself too clearly, but you can see the result I am aiming for in the example code provided above. Basically I want this recursion to happen *indefinitely*, i.e. I don't have to have a LESS code for each depth level, so much as you don't write every level ofyour `for` or `while` statements. – Sunyatasattva Apr 17 '13 at 12:08
  • OK, well still I would add an additional argument to the mixin, and assign the new value to the variable `@parent` at each scope, and use it in the nested rules recursively. I added this solution to my answer above. – Martin Turjak Apr 17 '13 at 13:48