I want to create a variable with classes like so
$multi: foo, bar, baz
And I want to created a combined selector like so:
.foo, .bar, .baz {}
I am used the indented syntax (don't know if that would matter). The reason I want the combined selector generated from the variable: I need to do calculations based on how many classes are defined there. Please don't give a suggestion like (use extends!) because that will require me to make another class. I need to be able to get close or exactly to the regular combined selector output.
I had the same issue as the OP, and this was the first search result I found, so now that I’ve figured it out, I’ll post my solution, even though the question is 1.5 years old.
Here’s what I found out: in order to use a variable as a selector, you use SASS interpolation, which works perfectly with comma-separated strings. However, if you want the variable that holds your selectors to be a list (so that you can use list functions on it, e.g. length($multi)), the interpolation will generate a malformed selector.
So, the simple solution is to first define your variable as a list, then when you need to use it as a selector, convert that list into a comma-separated string:
$multi: ".foo", ".bar", ".baz"
$multi-selector: ""
#each $selector in $multi
#if $multi-selector != ""
$multi-selector: $multi-selector + ", "
$multi-selector: $multi-selector + $selector
#{$multi-selector}
//CSS properties go here
You may want to abstract the list-to-comma-separated-string functionality into a function (note: SASS’s join function doesn’t do this; it joins two lists into a new one). Here’s one possible implementation:
#function unite($list, $glue: ", ")
#if length($list) == 1
#return $list
$string: ""
#each $item in $list
#if $string != ""
$string: $string + $glue
$string: $string + $item
#return $string
At which point the original code can be as concise as:
$multi: ".foo", ".bar", ".baz"
#{unite($multi)}
//CSS properties go here
(code from previous answer) Cool function! Usefull. I just wanted to add some SCSS syntax, to give an opportunity to people to use it:
#function unite($list, $glue: ", ") {
#if length($list) == 1 {
#return $list;
} #else {
$string: "";
#each $item in $list {
#if $string != "" { $string: $string + $glue; }
$string: $string + $item;
}
#return $string;
}
}
At which point the original code can be as concise as:
$multi: ".foo", ".bar", ".baz"
#{unite($multi)}
//CSS properties go here
Compass offers a built-in sprite mixin that does just this. If you don't want to use all of Compass, you could look into their source to see how they are doing it.
Related
I have a long list of classes I wish to use in a couple of ways.
The list looks something like this (but much longer):
$my-components: '.some-component', '.some-other-component', '.another-component';
One of the ways I need to use this list of class names in SASS (scss), which I can't figure out, is to create a long chained selector of :not()s. The final rendered output should look like this:
.parent {
> * {
&:last-of-type:not(.some-component):not(.some-other-component):not(.another-component):not(etc) {
// style rules
}
}
}
(The goal being to select the last child element of .parent that doesn't have one of the classes in the list).
Question: How can I make the above code DRY by using the $my-components variable?
Note 1: The loop's output needs to be able to be appended to that &:last-of-type, as in above example.
Note 2: I'm using the $my-components variable already in a different function, so I'd like to keep it in the same format if possible.
Note 3: I know this seems hacky and stupid, and that I should just give all of those elements a common shared class instead. But unfortunately I can not currently modify that part of the DOM.
Use a #each loop
scss:
$my-components: '.some-component', '.some-other-component', '.another-component';
.parent {
> * {
$selector: '';
#each $component in $my-components {
$selector: $selector + ":not(#{$component})"
}
&:last-of-type#{$selector} {
color: blue;
}
}
}
css:
.parent > *:last-of-type:not(.some-component):not(.some-other-component):not(.another-component) {
color: blue;
}
What's happening ?
I define a new string variable $selector.
During the #each loop, I'm concatening the string with :not(#{$component}) to add your new selector.
I need to build a function where the argument passed to the function is a variable list which in some cases may contain only one item, like following:
$example-list-1: grey 20; // this is a single list item
$example-list-2: grey 20, grey 30; // this is a list with two items separated by comma
#function box-shadow($params-list) {
// ...
// here comes the problem
#each $item in $params-list {
// if $example-list-1 is passed as argument it will be considered a list with two items!
}
}
In order to avoid that problem I need to perform a check to see what kind of list was passed as argument, and in case of a list with one item I have to fake it like having multiple items, like this:
#function box-shadow($params-list) {
#if (params-list has only one item) {
$params-list: append($params-list, null, comma);
}
// problem solved
#each $item in $params-list {
// perform the magic
}
#return $result;
}
So, the question is how to check if params-list has only one item? Or, is there any workaround to solve this problem?
EDIT:
I found sort of a viable hack, to add an empty list item separated by comma, like this:
$example-list-1: grey 20, ();
The thing is that I can use this hack just by manually edit the variables, but not programatically! So the question remains...
I found a function inside the docs, it can be used to perform the check if separator is comma, and if not the list is rebuilded as a comma separated list:
#use "sass:list";
//...
#function box-shadow($params-list) {
// check if separator is comma, if not rebuild the list
#if list.separator($params-list) != comma {
$new-list: ();
$params-list: append($new-list, $params-list, comma);
#debug list.separator($params-list);
}
// loop now works as expected
#each $item in $params-list {
// code
}
}
EDIT:
I also wrote two helper functions, can be used when a list is expected in some format:
// convert to comma separated list
#function comma-separated-list($list) {
#if list.separator($list) != comma {
$new-list: ();
$list: append($new-list, $list, comma);
// #debug list.separator($list);
}
#return $list;
}
// convert to space separated list
#function space-separated-list($list) {
#if list.separator($list) != space {
$new-list: ();
$list: append($new-list, $list, space);
// #debug list.separator($list);
}
#return $list;
}
I'm trying to create a mixin in Sass to generate multiple background, problem is the number of background is unknow, it's can be 3, 4 or even 5. Here what I try and fail.
#mixin multiBg($page: index, $sec: sec01,$from: 1, $to: 3, $type: jpg){
$url: (); // i'm try to create a empty array first
$newUrl: null; // and an empty variable
#for $i from $from through $to {
$newUrl: append($url, url(../img/#{$page}/#{$sec}_bg0#{$i}.#{$type})); // then append value to variable;
}
background: $newUrl;
}
#sec05 {
#include multiBg(index,sec05);
}
current output:
background: url(../img/index/sec05_bg03.jpg);
expected output:
background: url(../img/sec05_bg01.jpg),url(../img/sec05_bg02.jpg), url(../img/sec05_bg03.jpg);
I don't know how to fix this problem since i'm still learing SASS. Can someone enlighten me please.
You're on the right track! But your syntax and logic are slightly off. Here's what I came up with:
#mixin multiBg($page: index, $sec: sec01, $from: 1, $to: 5, $type: jpg) {
$url_list: ();
#for $i from $from through $to {
// I broke constructing the url and adding it to the set into two steps here.
// We could do this all at once, but separating it can make it easier to read.
// First, generate the url.
$url_string: url(../img/#{$page}/#{$sec}_bg0#{$i}.#{$type});
// Then add it to the list (the 'comma' part is important)
$url_list: append($url_list, $url_string, comma);
}
// Done looping? Output the final list!
background-image: $url_list;
}
That seems to return what you're looking for. Here are the official docs on list functions - I always forget one or two, may be useful for you too.
Also, since you mentioned you're new to sass - check out Sassmeister if you haven't already. It's a handy little sandbox for quickly prototyping and trying things out in sass; similar to Codepen but a bit more specialized. It's what I used to experiment with this question.
This is a cleaner answer, I believe.
#mixin image-resolve($image) {
$images: ();
#each $i in $image {
$path: ();
$images: append($images, $path, comma);
}
background-image: $images;
}
It is necessary to perform a search and replace strings in css file. And found only the title picture with.
While the search is done so with the exception of
/:(\s*)url\(((.(?!.*https:|.*http:|.*base64|.*data:image))*)\)/ig
and replace
:$1url(\'../dist/img/vendor/$2\')
In this case, I replace the path in a similar way. And I get this result
background-image: url('../dist/img/vendor/"../images/preloader.gif"');
A need of a string
background-image: url("../images/preloader.gif");
get
background-image: url('../dist/img/vendor/preloader.gif');
Find
/(url\(\s*[\"\'])(?:[^\"\']+\/)?([^\/\"\']+[\"\']\s*\))/ig
Replace
$1../dist/img/vendor/$2
Demo https://regex101.com/r/kU7cC9/3
Using a css parser is a better way if you want among other things to avoid quotes problems (single quotes, double quotes, no quotes).
As an aside, the background-image CSS property isn't the only one that can contain a path to an image file, since the background property can compile all the data from other background- properties.
An example with sabberworm PHP CSS Parser that automatically encloses paths between double quotes:
require __DIR__ . '/vendor/autoload.php';
define('NEW_PATH', '../dist/img/vendor/');
$oCssParser = new Sabberworm\CSS\Parser($css);
$oCssDocument = $oCssParser->parse();
$properties = ['background-image', 'background'];
foreach($oCssDocument->getAllRuleSets() as $oRuleSet) {
foreach($properties as $property) {
$oCssRules = $oRuleSet->getRules($property);
foreach ($oCssRules as $oCssRule) {
$value = $oCssRule->getValue()->__toString();
if (!preg_match('~https?:|base64|data:image~S', $value)) {
$value = preg_replace('~url\("\K(?:[^"/]*/)*~', NEW_PATH, $value);
$oCssRule->setValue($value);
}
}
}
}
echo $oCssDocument->render(Sabberworm\CSS\OutputFormat::createPretty());
I'd like to access variable within an #each loop using defined value like in the following example:
$car:true;
$people:false;
$job:false;
#mixin options($someval){
#each $prefix in car,people,job{
#if $#{$prefix} == true{
//some CSS...
}
}
}
Variable would be a sort of "semaphores" that define whether print or not Css rules.
My big doubt is how can I check over dynamically defined variables name ?
I've tried with $#{$prefix} but it doesn't work.
EDIT ---------------------------
I'd like to obtain this CSS
car-something: 34px;
Where the word "car" is taken from $prefix and in the first round of #each loop $#{$prefix} becomes $car
The problem is on $#{$prefix} ... it doesn't work :P i get an error
This is an old question but since it has no valid answer, here it is
You need to pass a map to #each
$car:true;
$people:false;
$job:false;
#mixin options($someval){
#each $key, $val in (car: $car, people: $people, job: $job) {
#if $val == true{
#{$key}-something: $someval
}
}
}
test {
#include options(34px)
}
Instead of trying to interpolate a variable name, pass a list to the mixin.
$car: true;
$people: false;
$job: false;
$thing-list: $car, $people, $job;
#mixin thingamajig($thing-list) {
#each $thing in $thing-list {
#if $thing {
// Some CSS
}
}
}