Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a variable from inside a mixin declaration into the attached content block?

In Ruby, you can easily pass a variable from inside a method into the attached code block:

def mymethod
  (1..10).each { |e| yield(e * 10) } # Passes a number to associated block
end

mymethod { |i| puts "Here comes #{i}" } # Outputs the number received from the method

I would like to do the same thing in SASS mixin:

=my-mixin
  @for $i from 1 to 8
    .grid-#{$i}
      @content

+my-mixin
  color: nth("red green blue orange yellow brown black purple", $i)

This code won't work because $i is declared inside the mixin declaration and cannot be seen outside, where the mixin is used. :(

So... How do i leverage variables declared inside the mixin declaration?

When i work with a grid framework and media queries, i need this functionality badly. Currently i have to duplicate what's inside the mixin declaration every time i need it, violating the DRY rule.

UPD 2013-01-24

Here's a real-life example.

I have a mixin that cycles through breakpoints and applies the provided code once for every breakpoint:

=apply-to-each-bp
  @each $bp in $bp-list
    +at-breakpoint($bp) // This is from Susy gem
      @content

When i use this mixin i have to use this $bp value inside @content. It could be like this:

// Applies to all direct children of container
.container > *
  display: inline-block

// Applies to all direct children of container,
// if container does not have the .with-gutters class
.container:not(.with-gutters) > *
  +apply-to-each-bp
    width: 100% / $bp

// Applies to all direct children of container,
// if container has the .with-gutters class
.container.with-gutters  > *
  +apply-to-each-bp
    $block-to-margin-ratio: 0.2
    $width: 100% / ($bp * (1 + $block-to-margin-ratio) - $block-to-margin-ratio)
    width: $width
    margin-right: $width * $block-to-margin-ratio

    &:nth-child(#{$bp})
      margin-right: 0

But this won't work, because the value of $bp is not available inside @content.

Declaring the variable before calling the mixin won't help, because @content is parsed once and before the mixin is parsed.

Instead, EACH time i need that, i have to do two ugly thighs:

  1. declare an ad-hoc mixin,
  2. write the cycle, violating the DRY principle:
// Each of the following mixins is mentioned in the code only once.
=without-gutters($bp)
  width: 100% / $bp

=with-gutters($bp)
  $block-to-margin-ratio: 0.2
  $width: 100% / ($bp * (1 + $block-to-margin-ratio) - $block-to-margin-ratio)
  width: $width
  margin-right: $width * $block-to-margin-ratio

  &:nth-child(#{$bp})
    margin-right: 0

// Applies to all direct children of container
.container > *
  display: inline-block

// Applies to all direct children of container,
// if container does not have the .with-gutters class
.container:not(.with-gutters) > *
  @each $bp in $bp-list
    +at-breakpoint($bp) // This is from Susy gem
      +without-gutters($bp)

// Applies to all direct children of container,
// if container has the .with-gutters class
.container.with-gutters  > *
  @each $bp in $bp-list  // Duplicate code! :(
    +at-breakpoint($bp)  // Violates the DRY principle.
      +with-gutters($bp)

So, the question is: is there a way to do this Ruby-style?

like image 208
Andrey Mikhaylov - lolmaus Avatar asked Dec 27 '12 11:12

Andrey Mikhaylov - lolmaus


People also ask

How do you pass arguments to mixin?

Variable argument is used to pass any number of arguments to mixin. It contains keyword arguments passed to the function or mixin. Keyword arguments passed to the mixin can be accessed using keywords($args) function which return values mapped to String.

How do you declare a mixin?

Mixins are defined using the @mixin at-rule, which is written @mixin <name> { ... } or @mixin name(<arguments...>) { ... } . A mixin's name can be any Sass identifier, and it can contain any statement other than top-level statements.

What is the difference between mixin and variable?

Mixin do some kind of styling which can be used again again in your whole styling and same you can use variable as much as you can. Functions and Mixins are different. One returns value while the other is used like a macro to substitute.

What is the content in mixin?

The content blocks are passed to the mixin for the placement inside the styles. This functionality is added in Sass version 3.2. Styles are included into the mixin in the @content directive location. The block of content is specified in the scope and the scope is passed in the mixin where block is defined.


2 Answers

Variables in Sass have scope to them. They're only visible in the block they were created in. If you want the variable to be accessible both inside and outside of the mixin, it has to be defined in the global scope:

$var: 0;

@mixin test {
    $var: $var + 1;
    color: red;
}

.test {
    $var: 5;
    @include test;
    @debug $var; // DEBUG: 6
}

As long as you don't care about the state of $var for very long, this should work out ok for your purposes.

For your example, this won't work because it looks like the @content is processed first. What you need is a mixin that's written differently:

@mixin test($properties...) {
    @for $i from 1 to 8 {
        .grid-#{$i} {
            @each $p in $properties {
                $list: nth($p, 2);
                @if length($list) > 1 {
                    #{nth($p, 1)}: nth($list, $i);
                } @else {
                    #{nth($p, 1)}: $list;
                }
            }
            @content;
        }
    }
}

.test {
    @include test(color (red green blue orange yellow brown black purple));
}

The generated CSS:

.test .grid-1 {
  color: red;
}

.test .grid-2 {
  color: green;
}

.test .grid-3 {
  color: blue;
}

.test .grid-4 {
  color: orange;
}

.test .grid-5 {
  color: yellow;
}

.test .grid-6 {
  color: brown;
}

.test .grid-7 {
  color: black;
}

A mixin like this can be fed any number of arguments and still allows you to use @content if you wish.

like image 141
cimmanon Avatar answered Oct 18 '22 13:10

cimmanon


I have run into this problem myself and AFAIK this is a current limitation in SASS.

like image 27
Phil Thomas Avatar answered Oct 18 '22 12:10

Phil Thomas