Question

At the front, I started with less today...

So any advice how to do that better is welcome!

I have following .less file:

.test(@target;@context) {
  @em: (@target / @context) * 1em;
}

.custom-field {
  position: relative;
  .test(30;16);
  padding-bottom: @em;
  .test(30;16);
  margin-bottom: @em;
  .test(320;16);
  max-width: @em;
}

I expect that padding-bottom and margin-bottom getting the value 1.875 and max-width: 20.

But thats the output:

.custom-field {
  position: relative;
  padding-bottom: 1.875em;
  margin-bottom: 1.875em;
  max-width: 1.875em;
}

I had the second parameter as an optional parameter at the start. But for testing I made it as simple as possible.

If I use the mixin even more often, I get a lot more weird results. At least for me they are weird :)

Has anyone an advice for me?

Was it helpful?

Solution

LESS variables "lazy load", which means the order they appear in the code does not matter, it is generally the "last" call that wins. What what I found odd in your case is that I would have expected them all to be 20em since that is the "last" setting of the value. However, I learned from posting this "issue" that it is specifically because it is being called by a mixin that it is the first value only that is set. Because of the lazy loading, it can affect the whole scope the variable is called with, and because a mixin will only set the value once no matter how many times called, you end up with what can seem like "odd" behavior.

You have at least four ways that may handle your situation, which will output what you desire.

(1) Set the Property in the Mixin

.test(@target;@context;@prop) {
  @{prop}: (@target / @context) * 1em;
}

.custom-field {
  position: relative;
  .test(30;16;padding-bottom);
  .test(30;16;margin-bottom);
  .test(320;16;max-width);
}

(2) Set a Global Javascript Function

Notice carefully how this is constructed/used. I got the idea from this thread after your comment about SASS. Various functions could be set up this way.

@setEm: `setEm = function(target,context) { return ((target/context)+'em'); }`;

.custom-field {
  position: relative;
  padding-bottom: ~`setEm(30,16)`;
  margin-bottom: ~`setEm(30,16)`;
  max-width: ~`setEm(320,16)`;
}

(3) Non-Javascript Solution with Pattern Matching

Notice carefully how this is constructed/used. This is more "verbose", but does avoid a call to the javascript environment (if such is desired, or if they at sometime remove inline javascript functionality [which has been discussed during various issues on the site]) and gives some good flexibility. Various functions can be set up with this as well (as illustrated).

/*make a global setter for local variable getter (this will get up to six  
/*values from the same caller function to avoid variable overlap; should you 
/*need more within a single scope block, just expand this).
*/

.setGetInstance(@num) {
  .-(@num);
  .-(1) { @getVar1: @setVar;}
  .-(2) { @getVar2: @setVar;}
  .-(3) { @getVar3: @setVar;}
  .-(4) { @getVar4: @setVar;}
  .-(5) { @getVar5: @setVar;}
  .-(6) { @getVar6: @setVar;}
}

/*Create various "function" mixins that use the global setter */

.setEm(@target;@context;@num) {
  @setVar: ((@target / @context) * 1em);
  .setGetInstance(@num);
}
.-100(@target;@num) {
  @setVar: (@target - 100px);
  .setGetInstance(@num);
} 

/*Use the function mixins (up to six per block in this example) */

.custom-field1 {
  position: relative;
  .setEm(30;16;1;);
  padding-bottom: @getVar1;
  .setEm(30;16;2);
  margin-bottom: @getVar2;
  .setEm(320;16;3);
  max-width: @getVar3;
}
.custom-field2 {
  .setEm(20;10;1;);
  padding-bottom: @getVar1;
  .setEm(10;10;2);
  margin-bottom: @getVar2;
  .minus100(1000;3);
  max-width: @getVar3;
}

CSS Output

.custom-field1 {
  position: relative;
  padding-bottom: 1.875em;
  margin-bottom: 1.875em;
  max-width: 20em;
}
.custom-field2 {
  padding-bottom: 2em;
  margin-bottom: 1em;
  max-width: 900px;
}

(4) Using Nested Blocks or Mixins

I learned another method from this comment which also resolves the issue in a different way.

.setEm(@target;@context) {
  @em: ((@target / @context) * 1em);
}

.custom-field {
  position: relative;
  & {padding-bottom: @em; .setEm(30;16);}
  & {margin-bottom: @em; .setEm(30;16);}
  & {max-width: @em; .setEm(320;16);}
}

However, this will produce multiple selector blocks in any version of LESS prior to 1.6.2, like so (1.6.2+ merges it all, see seven-phases-max's comment):

.custom-field {
  position: relative;
}
.custom-field {
  padding-bottom: 1.875em;
}
.custom-field {
  margin-bottom: 1.875em;
}
.custom-field {
  max-width: 20em;
}

So it is better to keep it as a local mixin if using an earlier version:

.custom-field {
  position: relative;
  .-() {padding-bottom: @em; .setEm(30;16);}
  .-() {margin-bottom: @em; .setEm(30;16);}
  .-() {max-width: @em; .setEm(320;16);}
  .-() 
}

Which will group it all:

.custom-field {
  position: relative;
  padding-bottom: 1.875em;
  margin-bottom: 1.875em;
  max-width: 20em;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top