12

I am trying to create a mixin that dynamically defines variables in LESS CSS, by actually assigning them a composite name.

The simplified use-case (not the real one):

.define(@var){
    @foo{var}: 0;
}

Then one would call the mixin as such:

.define('Bar'){
    @fooBar: 0;
}

Since this kind of string interpolation is possible while using selectors names, I was wondering if the same would be possible for variable names; so far, I have had no luck with various syntaxes I tried (other than the above, I tried escaping, quoting, using the ~ prefix and so on).

Edit

I just tried one more thing, and I feel I might be close; but I am experiencing an oddity of the LESS syntax. If I write this:

.define(@var){
    #namespace {
         @foo: @var;
    }
}

And then call it like so:

.define(0)

I can then use @foo in the usual namespaced fashion:

.test {
     #namespace;
     property: @foo; /* returns 0 */
}

However, the same doesn't apply in the case of a string-interpolated selector:

.define(@var, @ns){
    #@{ns} {
         @foo: @var;
    }
}

.define(0, namespace);

.test {
     #namespace;
     property: @foo;
}

The above code gives me the following error:

Name error: #namespace is undefined

However, the string interpolation was successful and valid. As a matter of fact, if I take away the .test part and modify the above to output a test property, I can see that the CSS is parsed correctly. I mean:

.define(@var, @ns){
    #@{ns} {
         @foo: @var;
         prop: @foo;
    }
}

.define(0, namespace);

Outputs the following CSS:

#namespace {
    prop: 0;
}
Sunyatasattva
  • 5,619
  • 3
  • 27
  • 37

4 Answers4

12

This Cannot Be Done

What you desire to do is not currently possible in LESS. I can think of two possible "workarounds" if you know ahead of time what variable names you want to allow to be used (in other words, not fully dynamic). Then something like one of the following could be done:

Idea #1 (Variable Variables)

.define(@var) {
  @fooBar: 0;
  @fooTwo: 2;
  @fooYep: 4;

  @fooSet: 'foo@{var}';
}

.define(Two);
.test {
  .define(Bar);
  prop: @@fooSet;
}
.test2 {
  prop: @@fooSet;
}

Idea #2 (Parametric Mixins)

LESS

.define(@var) {
  .foo() when (@var = Bar) {
    @fooBar: 0;
  }
 .foo() when (@var = Two) {
    @fooTwo: 2;
  }
 .foo() when (@var = Yep) {
    @fooYep: 4;
  }
  .foo();
}

.define(Two);
.test {
  .define(Bar);
  prop: @fooBar;
}
.test2 {
  prop: @fooTwo;
}

CSS Output (for both ideas)

.test {
  prop: 0;
}
.test2 {
  prop: 2;
}

Conclusion

But I'm not sure how useful either would really be, nor do I know if it could have any real application in your actual use case (since you mention the above is not the real use case). If you want a fully dynamic variable in LESS, then it cannot be done through LESS itself.

ScottS
  • 71,703
  • 13
  • 126
  • 146
  • Thank you very much for your answer; unfortunately neither of your ideas would help me solve my real world case, though going for parametric mixins will cut some of my code down. I tried another solution, though, you can check the **edit**: any thoughts on that? – Sunyatasattva Aug 04 '13 at 02:59
  • Your edit with the namespace idea suffers from the fact that at present, [LESS does not support using dynamically generated classes as mixins](https://github.com/less/less.js/issues/1399). A namespace is basically a mixin. – ScottS Aug 04 '13 at 10:05
  • @Sunyatasattva: I'm also not seeing what advantage the namespacing gives you, when you could simply call `.define(0);` inside any block of code you would have put the `#namespace`. Both allow you to use the `@foo` variable at that point. You may need to describe further what you actually are trying to do and why you are trying to do it. – ScottS Aug 04 '13 at 10:26
  • Thanks for the link to the issue. Indeed you are right, using the namespace would just save me a little bit of code; at the beginning when I tried this approach I hoped I could call `prop: #namespace > @fooBar` but indeed this is not the case. Thanks for your help. – Sunyatasattva Aug 04 '13 at 13:51
  • Fantastic answer... Needed a moment to get my head around this but well worth the time! Thanks – Dominik Jul 14 '14 at 00:30
2

I`m not really sure for what you want to use this, but one of my suggestion is based on @ScottS answer. On my real world, I need to create a web app, where it would show several brands and each brand have their own text color, background and so on... so I started to chase a way to accomplish this in LESS, what I could easily do on SASS and the result is below:

LESS

// Code from Seven Phase Max
// ............................................................
// .for
.for(@i, @n) {.-each(@i)}
.for(@n)     when (isnumber(@n)) {.for(1, @n)}
.for(@i, @n) when not (@i = @n)  {
    .for((@i + (@n - @i) / abs(@n - @i)), @n);
}

// ............................................................
// .for-each

.for(@array)   when (default()) {.for-impl_(length(@array))}
.for-impl_(@i) when (@i > 1)    {.for-impl_((@i - 1))}
.for-impl_(@i)                  {.-each(extract(@array, @i))}


// Brands
@dodge : "dodge";
@ford : "ford";
@chev : "chev";

// Colors
@dodge-color : "#fff";
@ford-color : "#000";
@chev-color : "#ff0";

// Setting variables and escaping than
@brands: ~"dodge" ~"ford" ~"chev";

// Define our variable   
.define(@var) {
  @brand-color: '@{var}-color';
}

// Starting the mixin
.color() {
    // Generating the loop to each brand
    .for(@brands); .-each(@name) {
        // After loop happens, it checks what brand is being called
        .define(@name);
         // When the brand is found, match the selector and color
        .brand-@{name} & {
            color: @@brand-color;
        }
    }
}

.carColor {
    .color();
}

Te result will be:

CSS

.brand-dodge .carColor {
    color: "#fff";
}
.brand-ford .carColor {
    color: "#000";
}
.brand-chev .carColor {
    color: "#ff0";
}

This is very tricky and I had to use several elements to get what I needed, first used a set of mixins provided by Seven Phase Max and you can find it here and than, the @ScottS answer was the piece that was missing fro my puzzle... hope this helps you and others that need to create a set of Variables to be part of another variable and create a more dynamic less file.

You can copy my entire code and test at http://lesstester.com/

Paulo Griiettner
  • 1,115
  • 12
  • 18
  • Thank you for the comment. I will take a look at your code. My question is almost a year old and I cannot remember exactly what I was trying to achieve (I must have achieved in a different way). I will study a bit what you posted and also dig in my old code to see if it fits. – Sunyatasattva May 23 '14 at 03:13
  • Just in case, my only question is do you really need these variables there? (the variables are what makes all this so complicated). Personally I can't see any need for things like `@dodge-color` there at all (i.e. writing `dodge` in one place just to make yourself to have to write `dodge` in others?). For me it looks like these kind of stuff is perfectly solvable with something like [this](http://stackoverflow.com/q/23551080/2712740) or [this](http://stackoverflow.com/a/23660124/2712740) (+ any suitable combinations of those methods). – seven-phases-max May 27 '14 at 19:04
  • @seven-phases-max I understand your point here, but there are cases that we really need to generate classes dynamical... My example was very simple and in this case does not really make sense, but on my my real world the variable was needed, because we only wanted the set variables and let LESS do the rest. Another thing to note, on our case the CMS was setting the brand classes to the body tag and each page would have their own font color, background image and etc... – Paulo Griiettner May 28 '14 at 18:45
  • I still can't see why `@colors: dodge #fff, ford #000;` or similar can't be used instead of `@dodge-color: #fff; @color-ford: #000;`. Take *any* other programing language design patterns: when we need some repeatable stuff we define this data via arrays of structures (or structures of arrays etc.), we don't declare every single value we need as a global variable with a unique name (yet again, what for?). OK, never mind (it's seems to go offtopic already), I just wanted to mention that for me this whole idea looks like fundamentally wrong approach. – seven-phases-max May 28 '14 at 20:03
1

To follow up on the accepted answer you can also define a value for the variable by extending the .define() mixin as follows. This allows you to use a temporary set of variables within a rule.

.define(@var, @val) {
  .foo() when (@var = temp1) {
    @temp1: @val;
  }
 .foo() when (@var = temp2) {
    @temp2: @val;
  }
 .foo() when (@var = temp3) {
    @temp3: @val;
  }
  .foo();
}

.define(temp2, 2);
.test {
  .define(temp1, 0);
  prop: @temp1;
}
.test2 {
  prop: @temp2;
}

CSS Output

.test {
  prop: 0;
}
.test2 {
  prop: 2;
}

Here is a more complicated gist of how I use this in a mixin for generating responsive background images with background-size: cover; (and an IE8 fallback).

Tg7z
  • 258
  • 3
  • 15
1

I don’t have time to build out the examples, but none of the above are as quick and simple as defining a variable and then assembling the import based on it. Then just have multiple documents where the same variables are defined.

@whitelabel: 'foo';
@import 'whitelabel/@{whitelabel}/colors';
Kirk Strobeck
  • 17,984
  • 20
  • 75
  • 114