SASS mixing and passing a variable string that's a function name - sass

I'm expanding out a SASS mixin for generating background colours.
Here's my mixin:
#mixin color-columns ($count, $baseName, $startcolor) {
$loop_color : $startcolor;
#for $i from 0 through $count {
$loop_color : darken($loop_color, 8%);
.#{$baseName}-#{$i} {
background-color : $loop_color;
}
}
}
And i use it like this:
#include color-columns(5,'col', $blue);
What I want to do is pass in either 'darken' or 'lighten' as a variable so i can control with SASS colour function. Something like this:
#mixin color-columns ($count, $baseName, $startcolor, $change: 'darken') {
$loop_color : $startcolor;
#for $i from 0 through $count {
$loop_color : $change($loop_color, 8%);
.#{$baseName}-#{$i} {
background-color : $loop_color;
}
}
}
When i try that, my CSS for the first column is like this:
background-color: darken darken #005387, 8%, 8%;
I'm missing the connection on passing a variable that swaps the function.

Currently there is no support for defining dynamic function names. It has been requested several years ago and there is even an open pull request for this functionality but it is not yet merged.
However, it is possible to dynamically call a function with the built-in call($function, $args...) function. It allows you to avoid if-else cascades and can be used in your example like this:
$loop_color : call($change, $loop_color, 8%);

I ended up adding an #if to get this to work:
#mixin color-columns ($count, $startcolor, $start : darken) {
$loop_color : $startcolor;
#if $start == 'lighten' {
#for $i from 0 through $count {
$loop_color : lighten($loop_color, 5%);
.conBG-#{$i} {
background-color : $loop_color;
}
}
}
#else {
#for $i from 0 through $count {
$loop_color : darken($loop_color, 5%);
.conBG-#{$i} {
background-color : $loop_color;
}
}
}
}

Related

sass #for within #each, where #for is using key, value pair of #each

I was not able to find the answer to my question: WTF - why?!
Variables
$breakpointNames: (xs, sm, md, lg, xl);
$breakpointSizes: (480px, 667px, 845px, 1024px, 1280px);
$breakpoints: createMapOutOfLists($breakpointNames, $breakpointSizes);
$masonryLayoutColumnsCount: (2, 2, 3, 4, 4);
$masonryLayoutColumns: createMapOutOfLists($breakpointNames,
$masonryLayoutColumnsCount);
Mixin
#mixin media($min, $max: false) {
#if $max == false {
#if $min == xs {
#media (min-width: 0px) {
#content;
}
}
#media (min-width: #{map-get($breakpoints,$min)}) {
#content;
}
} #else if $min == 0 {
#media (max-width: (#{map-get($breakpoints,$max)} - 1)) {
#content;
}
} #else {
#media (min-width: #{map-get($breakpoints,$min)}) and (max-width: (#{map-get($breakpoints,$max)} - 1)) {
#content;
}
}
}
I wrote this:
#each $breakpoint, $column-count in $masonryLayoutColumns {
#include media($breakpoint) {
.masonry-tile {
width: 100% / $column-count;
}
}
#for $i from 1 through $column-count {
#include media($breakpoint) {
.masonry-tile:nth-child(#{$i}) {
-webkit-order: $i;
order: $i;
}
}
}
}
That threw following Error:
Error in plugin 'sass'
Message:
../../../app/Resources/assets/sass/functions/_map_generators.scss
Error: 2 is not an integer.
on line 4 of
../../../app/Resources/assets/sass/functions/_map_generators.scss
>> merge($map, (#{nth($listOfKeys, $index)}: #{nth($listOfValues, $index)}));
------------------------------------------^
That the error is at a wrong place or maybe entirely wrong, is another issue and currently irrelevant - to me - right now.
After some time of searching without results and trying to fix it on my own, I got it to work with the following:
#for $index from 1 through length($masonryLayoutColumns) {
$breakpoint: nth($breakpointSizes, $index);
$column-count: nth($masonryLayoutColumnsCount, $index);
#include media($breakpoint) {
.masonry-tile {
width: 100% / $column-count;
}
}
#for $i from 1 through $column-count {
#include media($breakpoint) {
.masonry-tile:nth-child(#{$i}) {
-webkit-order: $i;
order: $i;
}
}
}
}
Okay, that works, but I don't like it.
I prefer the first version.
Is there a good reason why the first version is not working? Or is it a bug?
Thanks in advance!
The only reason your code didn't work is because you must be passing a string in the map that you are creating.
$column-count should be an integer, in order to do your width manipulation (width: 100% / $column-count;)
I have attached a sample pen, with integer data, which will resolve your issue.
$masonryLayoutColumns: (
'760px': 8,
'990px': 12,
'1024px': 16
);
#each $breakpoint, $column-count in $masonryLayoutColumns {
#media(min-width: $breakpoint) {
.masonry-tile {
width: 100% / $column-count;
}
}
#for $i from 1 through $column-count {
#media(min-width: $breakpoint) {
.masonry-tile:nth-child(#{$i}) {
-webkit-order: $i;
order: $i;
}
}
}
}
https://codepen.io/anon/pen/LLBPaP
Note: I don't have your mixin's so I am just writing a generic media query.

Conditionally output a part of SCSS rule selector

I would like to specify an additional default shortcut class to a set of classes, similarly to that
#each $pos, $some-css-rules in ("left": ..., "right": ..., ...) {
#if $pos == "left" {
.block,
}
.block-#($pos) {
...
}
}
that would be outputted as
.block,
.block-left {
...
}
.block-right {
...
}
However, it will stumble over .block, syntax error.
.block-left cannot be replaced here with .block.left because $pos will collide with existing classes (.left, etc).
I would prefer to avoid .block { #extend .block-left } if possible, there is a considerable amount of similar rules that will gain a lot of WET code this way.
Is there a way to conditionally output a part of rule selector? How can both SCSS and CSS be kept DRY in a pattern like that?
I'm not sure if I understand the question but I achieve the output CSS based on your code. I put the #if directive inside the selector to compare with $pos variable. Here is my code:
SASS
#each $pos, $some-css-rules in ("left": red, "right": blue) {
.block-#{$pos} {
#if $pos == "left" {
#at-root .block, &{
color:$some-css-rules;
}
}
#else{
color:$some-css-rules;
}
}
}
Output
.block, .block-left {
color: red;
}
.block-right {
color: blue;
}

Using passed Variable as Key/Value pair in Mixins

I tried looking this up, but have not had success. I could not be looking with the right search parameters.
I'm creating a mixin in SASS the will allow me to create keyframes by passing the animation name, from value and to value. Here's an example:
#mixin keyframes($name, $from, $to) {
#-webkit-keyframes #{$name} {
from {
left: $from
}
to {
left: $to
}
}
}
This is a shorter version as I would also add lines for #-moz-keyframes and #keyframes. I prefer this method so that I'm not having to repeat "from" and "to" in the animations and having the mixin just grab it using #content, but I also don't want to assume that "left" is the only property that is going to be affected.
What I'd like to do is treat both $from and $to variables as Objects so that they can contain a series of key/value pairs. When I attempt this:
$mixin keyframes($name, $from, $to) {
#-webkit-keyframes #{$name} {
from {
$from
}
to {
$to
}
}
}
...I get compile errors because it's excepting a key/value pair and not a variable.
Is there a way to tell SASS to treat $from and $to as a series of key/value pairs? I tried #{$from} already and it still throws that compile error.
Thanks!
You can't do that because property/values aren't strings. You would have to write it using mappings, like this:
#mixin keyframes($name, $from, $to) {
#-webkit-keyframes #{$name} {
from {
#each $prop, $val in $from {
#{$prop}: $val;
}
}
to {
#each $prop, $val in $to {
#{$prop}: $val;
}
}
}
}
#include keyframes(foo, (top: 10px), (top: 50px));
However, I would recommend not doing this at all if your goal is to write a flexible mixin. Just write out your own from/to statements:
#mixin keyframes($name) {
#-webkit-keyframes #{$name} {
#content
}
}
#include keyframes(foo) {
from {
top: 10px;
}
to {
top: 50px;
}
}

SASS & list iteration / delimiter

I've got a pattern where I create a list of lists to iterate over, as an basic example:
$carouselContent : "carousel-content-1" "buying_carousel_image_1.jpg",
"carousel-content-2" "buying_carousel_image_2.jpg";
My iteration (inside of a mixin) then looks like:
#each $carousel in $carouselContent {
$baseClass: nth($carousel, 1);
$image: nth($carousel, 2);
.#{$baseClass} {
....
}
}
I just came across a page that presently only has 1 item in the carousel. I'd like to keep with the pattern, but I'm not sure how to do so. If I iterate over:
$carouselContent : "carousel-content-1" "growing_carousel_image_1.jpg";
SASS treats that as a 2 item list. I could work around that by adding an empty item to my list, then adding a check against empty string, e.g.
$carouselContent : "carousel-content-1" "growing_carousel_image_1.jpg","" "";
But that seems hacky... so I figured there has to be a way to do this that I'm unaware of.
In Sass 3.3.0, all you need to do is have a trailing comma to signify that what you have is a list with one item in it:
$carouselContent : "carousel-content-1" "buying_carousel_image_1.jpg", ;
#each $carousel in $carouselContent {
$baseClass: nth($carousel, 1);
$image: nth($carousel, 2);
.#{$baseClass} {
color: red;
}
}
Generates:
.carousel-content-1 {
color: red;
}
Sass 3.3.0 is still undergoing development, but you can play with it now by upgrading to the latest edge version via gem install sass --pre. However, if you're willing to upgrade to 3.3, you may want to look at mappings instead (see: the change log)
You can use an #if directive to check if the first element of your list is also a list with type-of() (and only then use the loop). Something along these lines (I separated the block from inside your loop as a mixin):
#mixin do_car($carousel) {
$baseClass: nth($carousel, 1);
$image: nth($carousel, 2);
.#{$baseClass} {
/* ... */
}
}
#if (type-of(nth($carouselContent,1)) == list) {
#each $carousel in $carouselContent {
#include do_car($carousel);
}
} #else {
#include do_car($carouselContent);
}
DEMO
If your items are numbered sequentially, you can use a for loop instead:
$carouselImages: 2;
#for $i from 1 through $carouselImages {
.#{carousel-content-#{$i}} {
background: url(buying_carousel_image_#{$i}.jpg);
}
}
Output:
.carousel-content-1 {
background: url(buying_carousel_image_1.jpg);
}
.carousel-content-2 {
background: url(buying_carousel_image_2.jpg);
}
Alternately:
//$carouselContent : "buying_carousel_image_1.jpg", "buying_carousel_image_2.jpg";
$carouselContent : "buying_carousel_image_1.jpg";
#for $i from 1 through length($carouselContent) {
.#{carousel-content-#{$i}} {
background: url(nth($carouselContent, $i));
}
}

How do I combine a range of sass classes that are created in a loop

I am using SASS to construct a range of classes from variables passed into a mixin.
#mixin classes($key, $num) {
#for $i from 1 through $num {
[class*=#{$key}-#{$i}] {
#content
}
}
}
#include classes(grid, 8) {
width:100px;
}
It currently makes the classes like I want, but all as 8 separate classes (which are identical in #contents. Is there a way to merge them all together so I get:
[class*=grid-1],
[class*=grid-2],
....
[class*=grid-8],
{
width:100px;
}
I'm not sure if it's even possible to do this? Any pointers would be greatly appreciated.
Thanks,
Carl
I found the answer eventually
$classes: ();
#for $i from 1 through $cols {
$classes: join($classes, unquote("#{$prefix}#{$i} "), comma);
}
#{$classes} {
float: left;
margin-right: $gutterPercent;
width: $columnWidth;
}

Resources