1

I would like the following mixin to understand that I want to pass multiple arguments instead of using 3px, 0, 0, 3px as a single argument.

Note: I do realize that I could make @myRadius: 3px and do .border-radius( @myRadius, 0, 0 @myRadius );. I just don't want to.

Sample Code (paste into LESSTESTER to see output):

.border-radius(@topright: 0, @bottomright: 0, @bottomleft: 0, @topleft: 0) {
     -webkit-border-top-right-radius: @topright;
  -webkit-border-bottom-right-radius: @bottomright;
   -webkit-border-bottom-left-radius: @bottomleft;
      -webkit-border-top-left-radius: @topleft;

     -moz-border-radius-topright: @topright;
  -moz-border-radius-bottomright: @bottomright;
   -moz-border-radius-bottomleft: @bottomleft;
      -moz-border-radius-topleft: @topleft;

     border-top-right-radius: @topright;
  border-bottom-right-radius: @bottomright;
   border-bottom-left-radius: @bottomleft;
      border-top-left-radius: @topleft;
}

@myRadius: 3px, 0, 0, 3px;

div {
    .border-radius( @myRadius );
}
Bryan Downing
  • 15,194
  • 3
  • 39
  • 60

2 Answers2

4

In short: no, currently this is not possible (a variable always goes as one mixin argument). (The "list to arguments expansion" feature is actually implemented and may appear in Less 2.0 but it needs some upvotes, see #1857 - do not hesistate to +1 there).

Workarounds:

1

You can create 'smart arguments handling' via mixin specializations (see Pattern Matching). E.g. (Less 1.6+):

.border-radius(@tr: 0, @br: 0, @bl: 0, @tl: 0) when (default()) {
    border-top-right-radius:    @tr;
    border-bottom-right-radius: @br;
    border-bottom-left-radius:  @bl;
    border-top-left-radius:     @tl;
}

.border-radius(@values) when (length(@values) = 4) {
    .border-radius(
        extract(@values, 1),
        extract(@values, 2),
        extract(@values, 3),
        extract(@values, 4));
}

// you also have to provide specializations for
// (length(@values) = 2) and (length(@values) = 3)
// cases if you need to handle them

// ...............................................
// usage:

@myRadius: 3px, 0, 0, 3px;

a {
    .border-radius(1px, 2px);
}

b {
    .border-radius(@myRadius);
}

2

Same as above, just a bit less verbose variant (Less 1.5+):

.border-radius(@values...) {
    .-() {@tr: 0; @br: 0; @bl: 0; @tl: 0} .-();
    .-() when (length(@values) > 0) {@tr: extract(@values, 1)}
    .-() when (length(@values) > 1) {@br: extract(@values, 2)}
    .-() when (length(@values) > 2) {@bl: extract(@values, 3)}
    .-() when (length(@values) > 3) {@tl: extract(@values, 4)}

    border-top-right-radius:    @tr;
    border-bottom-right-radius: @br;
    border-bottom-left-radius:  @bl;
    border-top-left-radius:     @tl;
}

// ...............................................
// usage:

@myRadius: 3px, 0, 0, 3px;

a {.border-radius(1)}
b {.border-radius(1, 2)}
c {.border-radius(1, 2, 3)}
d {.border-radius(1, 2, 3, 4)}
e {.border-radius(1 2 3 4)}
f {.border-radius(@myRadius)}
// etc.

3

Not a workaround but "just in case" remark, if the use case is limited to basic CSS properties it's actually questionable if you really need default values to be 0 instead of actual CSS "default value" (i.e. why would we need .border-radius(1, 2) to be expanded to border-radius: 1 2 0 0; and not to border-radius: 1 2;?). Contrary I would expect the mixing to follow CSS syntax and that way it can be as simple as:

.border-radius(@values...) {
    // ...
    border-radius: @values;
}

// ...............................................
// usage:

@myRadius: 3px 0 0 3px; // no commas here

a {.border-radius(1)}
b {.border-radius(1, 2)}
c {.border-radius(1, 2, 3)}
d {.border-radius(1, 2, 3, 4)}
e {.border-radius(1 2 3 4)}
f {.border-radius(@myRadius)} 

P.S. And as my always: if your use cases for this kind of stuff are limited to vendor prefixes, consider to add an autoprefixing tool into your build chain to stop wasting your time for writing these vendorizing preprocessor mixins (see also).

Community
  • 1
  • 1
seven-phases-max
  • 11,765
  • 1
  • 45
  • 57
  • Very nice examples @seven-phases-max. I am not sure, but I think you can't have a mixin named `.-()` (you might want to rename it `._()` or `.tmp()`) – helderdarocha Mar 14 '14 at 15:51
  • Less variables and selectors (and thus mixins if they begin with `.` or `#`) may contain `A...Z`, `a...z`, `0...9`, `_` and `-` characters and begin with any of those. E.g.: `.-`, `.0`, `.444-87-80` are valid mixin names (and `@-`, `@42` are valid variables :) – seven-phases-max Mar 14 '14 at 16:49
  • Ok. I mentioned that because I once had a problem with a mixin I called `.-()` and I had to change it to `._()` to make it work. But that was a while ago. Maybe it supports it now. – helderdarocha Mar 14 '14 at 17:02
  • Very refreshing answer, well done! Option 3 is clearly the best for the border-radius situation (I can't remember why I wrote the mixin to assign each corner explicitly). In any case, my sample code was there just to illustrate the point. Option 1 is way cool, and thanks for pointing out that GitHub issue. Also, thanks for reminding me that I don't even need to vendor prefix `border-radius` anymore. Also, thanks for pointing me at Autoprefixer (I really need to get that installed...everywhere...forever). In short, you're a badass. Cheers! – Bryan Downing Mar 14 '14 at 20:46
0

There is a way to "store multiple arguments in a variable," but the mixin must be set up to handle it, and the variable definition must be set up correctly as well.

Using LESS 1.7.0+

Consider this:

@callPropterySet: {@props();};

.border-radius(@topright: 0; @bottomright: 0; @bottomleft: 0; @topleft: 0; @varSet: @callPropterySet) {
    @varSet();
    @props: {
     -webkit-border-top-right-radius: @topright;
  -webkit-border-bottom-right-radius: @bottomright;
   -webkit-border-bottom-left-radius: @bottomleft;
      -webkit-border-top-left-radius: @topleft;

     -moz-border-radius-topright: @topright;
  -moz-border-radius-bottomright: @bottomright;
   -moz-border-radius-bottomleft: @bottomleft;
      -moz-border-radius-topleft: @topleft;

     border-top-right-radius: @topright;
  border-bottom-right-radius: @bottomright;
   border-bottom-left-radius: @bottomleft;
      border-top-left-radius: @topleft;
  };
}

@myRadii: {
    @topright: 3px; 
    @bottomright: 0; 
    @bottomleft: 0; 
    @topleft: 3px;
    @callPropterySet(); //have to include this call again
};

I've set a global variable to a ruleset to call for the property setting in a mixin, the @callPropterySet: {@props();};. This allow me to set it as a default for my redefined mixin that calls it using its own new variable, @varSet. The actual ruleset that @props() itself accesses through @varSet is defined inside the mixin itself. This then allows the flexibility to call the mixin various different ways, like so:

div {
    .border-radius(@varSet: @myRadii); //call with the single variable
}

.test {
    .border-radius(10px, 5px, 4px, 2px); //call with properties
}

.test2 {
    .border-radius(); //call with defaults
}

All will produce expected output. Note the key to having this work, however, is to have that single variable also have the @callPropterySet(); as part of its ruleset. Apparently at present (LESS 1.7) variables defined in a ruleset are considered only inside the scope of that ruleset (they do not expand into the scope like a mixin does).

So you cannot presently do something like this and have it work:

.border-radius(@topright: 0; @bottomright: 0; @bottomleft: 0; @topleft: 0; @varSet...) {
    @varSet();
     -webkit-border-top-right-radius: @topright;
  -webkit-border-bottom-right-radius: @bottomright;
   -webkit-border-bottom-left-radius: @bottomleft;
      -webkit-border-top-left-radius: @topleft;

     -moz-border-radius-topright: @topright;
  -moz-border-radius-bottomright: @bottomright;
   -moz-border-radius-bottomleft: @bottomleft;
      -moz-border-radius-topleft: @topleft;

     border-top-right-radius: @topright;
  border-bottom-right-radius: @bottomright;
   border-bottom-left-radius: @bottomleft;
      border-top-left-radius: @topleft;
}

@myRadii: {
    @topright: 3px; 
    @bottomright: 0; 
    @bottomleft: 0; 
    @topleft: 3px;
};

Because it is like doing this:

.border-radius(@topright: 0; @bottomright: 0; @bottomleft: 0; @topleft: 0; @varSet: @callPropterySet) {
    {
      @topright: 3px; 
      @bottomright: 0; 
      @bottomleft: 0; 
      @topleft: 3px;
    }
     -webkit-border-top-right-radius: @topright;
  -webkit-border-bottom-right-radius: @bottomright;
   -webkit-border-bottom-left-radius: @bottomleft;
      -webkit-border-top-left-radius: @topleft;

     -moz-border-radius-topright: @topright;
  -moz-border-radius-bottomright: @bottomright;
   -moz-border-radius-bottomleft: @bottomleft;
      -moz-border-radius-topleft: @topleft;

     border-top-right-radius: @topright;
  border-bottom-right-radius: @bottomright;
   border-bottom-left-radius: @bottomleft;
      border-top-left-radius: @topleft;
}

Which puts the ruleset in a different scope than the properties you want to set, and so they never get set.

ScottS
  • 71,703
  • 13
  • 126
  • 146