Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterating mixin only works first time it's called #2849

Closed
lukechilds opened this issue Mar 22, 2016 · 4 comments
Closed

Iterating mixin only works first time it's called #2849

lukechilds opened this issue Mar 22, 2016 · 4 comments

Comments

@lukechilds
Copy link

I have the following mixin:

.prepend-classes(@rules) {
  @classes: foo, bar, baz;
  .prepend-classes-iterate(@i: length(@classes), @rules) when (@i > 0) {
    .prepend-classes-iterate(@i - 1, @rules);
    @class: extract(@classes, @i);
    @rules();
  }
  .prepend-classes-iterate(length(@classes), @rules);
}

Which when used like this:

.prepend-classes({
  .@{class}-my-class{ background: red; }
});

Should output this:

.foo-my-class{
  background: red;
}
.bar-my-class{
  background: red;
}
.baz-my-class{
  background: red;
}

It works, but only the first time the mixin is called. Any time after that @{class} always returns the last value. So for this example running the exact same code a second time outputs this:

.baz-my-class{
  background: red;
}
.baz-my-class{
  background: red;
}
.baz-my-class{
  background: red;
}

I may be interpreting the docs incorrectly but it seems to me according to this part of the docs it should work as the variable is local to this mixin.

The line @class: extract(@classes, @i); always sets @class correctly. It's only when you try and output @class inside @rules the value isn't passed through correctly.

Working demo

@lukechilds
Copy link
Author

I've got this working now but I'm still not sure if this is a bug or not so I'll leave it open.

For anyone who stumbles accross this looking for a fix, setting @class in it's own block resolves the issue:

.prepend-classes(@rules) {
  @classes: foo, bar, baz;
  .prepend-classes-iterate(@i: length(@classes), @rules) when (@i > 0) {
    .prepend-classes-iterate(@i - 1, @rules);
    & {
      @class: extract(@classes, @i);
      @rules();
    }
  }
  .prepend-classes-iterate(length(@classes), @rules);
}

Working demo

@seven-phases-max
Copy link
Member

Yes, the result is expected (since after the first loop the variable is in the global/parent scope and then inside @rules it has the higher priority than the lower level variable defined within the second loop. Ref: #1316, #2435 (comment)). Thus why emulation of parametric rulesets (and mixins) via using outer variables as parameters is a dangerous thing. And yes, your workaround is what does the trick. Another safe "parametric ruleset" emulation could be something like:

.prepend-classes(@rules) {
  @classes: foo, bar, baz;
  .apply(@class, @rules) {@rules();}

  .-(@i, @rules) when (@i > 0) {
    .-(@i - 1, @rules);
    .apply(extract(@classes, @i), @rules);
  }
  .-(length(@classes), @rules);
}

Though in your particular case I wonder if you need a loop at all:

.prepend-classes(@rules) {
  .foo, .bar, .baz {
    @rules();
  }
}
.prepend-classes({
  &-red-class {background: red}
});

And vice versa...

@seven-phases-max
Copy link
Member

Closing as duplicate of #1316.

@lukechilds
Copy link
Author

The loop is needed in my particular use case as I do something different depending on the class, the above was just a simplified example to reproduce the issue.

Thanks for taking the time to explain what's going on here though, much appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants