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

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:
declare an ad-hoc mixin,
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?

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.

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

So this is currently unavailable in Sass.
There's a relevant ticket in the Sass issue queue: https://github.com/nex3/sass/issues/871 It's in the planned state but will probably not make it until at least Sass 4.0.

Related

Sass manipulate with #content

Is it possible to manipulate with #content magic variable in SASS?
I would like to replace some stuff in here before output.
Or maybe can I fill some variable with it?
The conclusion is that, I want to make an mixin #important that create both versions. Important, and no-important.
Input
.test {
#include important {
color: red;
text-align: left;
}
}
Expected output
.test {
color: red;
text-align: left;
}
.test-i {
color: red !important;
text-align: left !important;
}
No, you can't. But I quickly wrote you a mixin to make it work. It doesn't accepts multiple properties (yet).
First Note: I changed the mixin it now does accept multiple properties. Here is the Codepen.
Second Note: I updated the mixin adding multiple properties does no longer compile to different classes for each property, instead you get two versions, one without the !important suffix and one with.
This is the mixin:
#function return($state) {
#return if($state == '', '', '-i');
}
#mixin loop($name, $items...) {
#each $item in $items / 2 {
#each $state in ('', '!important') {
$suffix: return($state);
.#{$name}#{$suffix} {
#for $i from 1 through (length($items) / 2) {
#{nth($items, ($i * 2) - 1)}: #{nth($items, ($i * 2))} #{$state};
}
}
}
}
}
This is how you include it:
// #include loop([classname], [property], [value]);
#include loop(whateverClassname, color, red);
This is what it compiles to:
.whateverClassname {
color: red ;
}
.whateverClassname-i {
color: red !important;
}
This is what it now compiles to, when you use multiple properties at once:
#include loop(whateverClassname, color, red, background-color, green, display, flex);
.whateverClassname {
color: red ;
background-color: green ;
display: flex ;
}
.whateverClassname-i {
color: red !important;
background-color: green !important;
display: flex !important;
}
Conclusion: it works as expected and does no longer bloat your CSS.
Hope I could help you at least a little ;-)

Passing arguments from a mixin to a content block

This open issue in the Sass queue seems to imply passing arguments to #content is not a feature yet, but Susy 2 seems to be able to do this. Tracking down how it's done is a bit of a rabbit hole though and I haven't figured it out yet. Perhaps someone can shed some light with a straightforward example? I want to create a custom mixin that will inherit a layout passed from susy-breakpoint() using a custom map.
Example: Defining a 4 column layout in a global Sass map, will return a width of 100% when a span of 4 is specified inside susy-breakpoint()'s #content. When a custom layout of 8 cols is passed to directly tosusy-breakpoint() via the $layout argument, the nested span() mixin picks up the new layout. But a custom nested mixin will not pick up the new layout. Why?
#import 'susy';
$susy: (
columns: 4,
);
#mixin inherit-layout($layout: 4) {
columns: $layout;
}
#include susy-breakpoint(30em) {
// nested code uses an 4-column grid from global map
.global-cols {
#include span(4);
#include inherit-layout();
}
}
#include susy-breakpoint(48em, $layout: 8) {
// nested code uses an 8-column grid from $layout
.inherited-cols {
#include span(4);
#include inherit-layout();
}
}
Compiled CSS:
#media (min-width: 30em) {
.global-cols {
width: 100%;
float: left;
margin-left: 0;
margin-right: 0;
columns: 4;
}
}
#media (min-width: 48em) {
.inherited-cols {
width: 48.71795%;
float: left;
margin-right: 2.5641%;
columns: 4;
}
}
Update:
I've discovered that making the default variable for the inherit-value() mixin the value of columns key on the existing $susy map allows the mixin to grab context. But why? And why doesn't it work with a different map or outside of susy-breakpoint()?
See here: http://sassmeister.com/gist/d86e217aca3aa8337b83
Susy doesn't pass any arguments to the #content — instead, we change the global variable at the start of the content block, and then change it back at the end:
$example: 4;
#mixin local($local) {
$old: $example;
$example: $local !global;
#content
$example: $old !global;
}
// out here, $example == 4
#include local(12) {
// in here, $example == 12
}

sass #if statement based on a class

Can I somehow make use of a static class inside sass to style child elements based on a color variable defined?
Let's say I have a class named red, and I want to define a variable called $color: classname; or $color: #ff0000; based on that class.
If class is red then define an existing variable with a custom color so I can reuse that variable everywhere inside my scss files based on what class I have on the container.
Note that I have a limited number of colors that I need, and can define them inside sass.
Is this what you're looking for?
$colors : (red, blue, green); // array of colors
#each $color in $colors {
.#{$color} {
color: $color;
}
}
The output of the above SASS is
.red {
color: red;
}
.blue {
color: blue;
}
.green {
color: green;
}
If I understand correct your problem You could use a class red and extend this class when you need it.
$red: #FF0000;
.red {
color: $red;
}
.div {
#extend .red;
}
I believe what you are trying to do is:
In an example file called "base.scss":
$red: red;/*this could be a HEX, RGB, whatever*/
#import "other"
In the example file called "other.scss":
div
{
color: $red
}

Passing a list to a mixin in SASS as parameters?

I'm trying to make a SCSS stylesheet easily configurable by defining a set of constants that will be used in a number of mixins and with the Compass library. Ideally, I'd like to be able to do the following:
$item-bgs: linear-gradient(white, black), #ccc;
#mixin some-mixin() {
#include background-with-css2-fallback($item-bgs*);
}
The background-with-css2-fallback is a Compass mixin that accepts up to 10 params. I'm assuming that SASS does not currently support passing a list parameter as the argument list, otherwise Compass would probably use it, but I'm wondering if I can get the $item-bgs list to be the first 2 arguments to the background-with-css2-fallback mixin. Is there a way to do this currently, or is it even planned for SASS in the future?
It may not be supported by SASS natively, but Compass does support passing a list as the first argument to the background-with-css2-fallback mixin. If you look at the source for the mixin, you'll see that it uses a compact function that handles the logic for collapsing the arguments into a single list, whether passed individually or in a single list parameter.
For example, this works fine for me:
#import "compass";
$item-bgs: (linear-gradient(white, black), #ccc);
.test {
#include background-with-css2-fallback($item-bgs);
}
Examples of useing maps as arguments:
Example 1 (list)
#mixin transition($property...){
#if $property {
transition-property: $property;
}
#else {
transition-property: all;
}
transition-timing-function: ease-in-out;
transition-duration: .3s;
transition-delay: 0;
}
.btn {
color: black;
border: 1px solid black;
#include transition(color, border-color);
&:hover {
color: red;
border-color: red;
}
}
Example 2 (Custom params)
#use 'sass:meta';
#mixin example2($args...) {
#each $key, $value in meta.keywords($args) {
#{$key}: #{$value};
}
}
.shape {
#include example2($width:200px, $height:100px);
}
Example 3 (map)
#mixin colors($args:()) {
#if length($colors) > 0 {
#each $key, $val in $args{
.txt-#{$key} {
color: #{$value};
}
.bg-#{$key} {
background-color: #{$value};
}
}
}
}
$colors_map: (
primary: blue,
secondary: green,
accent: red,
light: white,
dark: black
);
#include colors($colors_map);

How do you remove units of measurement from a Sass mixin equation?

I've written a very simple Sass mixin for converting pixel values into rem values (see Jonathan Snook's article on the benefits of using rems). Here's the code:
// Mixin Code
$base_font_size: 10; // 10px
#mixin rem($key,$px) {
#{$key}: #{$px}px;
#{$key}: #{$px/$base_font_size}rem;
}
// Include syntax
p {
#include rem(font-size,14);
}
// Rendered CSS
p {
font-size: 14px;
font-size: 1.4rem;
}
This mixin works quite well, but I'm a bit unsatisfied with the include syntax for it. See, I would much rather pass a pixel value into the include statement instead of a simple number. This is a small detail, but it would add semantic meaning to the include statement that currently doesn't exist. Here's what I get when I try to pass a pixel value into the include statement:
// Include syntax
p {
#include rem(font-size,14px);
}
// Rendered CSS
p {
font-size: 14pxpx;
font-size: 1.4pxrem;
}
Once Sass sees a pixel value being passed into an equation, it outputs the 'px'. I want to strip that unit of measure out as if I were using parseFloat or parseInt in JavaScript. How does one do so inside of a Sass mixin?
Here is a function you could use. Based of Foundation global helper functions.
#function strip-unit($num) {
#return $num / ($num * 0 + 1);
}
Sample use:
... Removed for brevity ...
#mixin rem( $key: font-size, $val ) {
#{$key}: strip-unit( $val ) * 1px;
#{$key}: ( strip-unit( $val ) / $base-font-size ) * 1rem;
}
Removing units from a number is done by division, just like in Algebra.
$base-font-size: 10px;
$rem-ratio: $base-font-size / 1rem;
#mixin rem($key,$px) {
#{$key}: $px;
#{$key}: $px/$rem-ratio;
}
// Include syntax
p {
#include rem(font-size,14px);
}
// Rendered CSS
p {
font-size: 14px;
font-size: 1.4rem;
}
Units in Sass can be treated as in real math, so px/px/rem goes to just rem. Enjoy it!

Resources