Checking if a variable is defined in SASS - ruby

AS the title says I am trying to check whether a variable is defined in SASS. (I am using compass if that makes any different difference)
I've found the Ruby equivalent which is:
defined? foo
Gave that a shot in the dark but it just gave me the error:
defined": expected "{", was "?
I've found a work around (which is obviously just to define the variable in all cases, which in this case it actually makes more sense) but I'd really like to know if this is possible for the future

For Sass 3.3 and later
As of Sass 3.3 there is a variable-exists() function. From the changelog:
It is now possible to determine the existence of different Sass constructs using these new functions:
variable-exists($name) checks if a variable resolves in the current scope.
global-variable-exists($name) checks if a global variable of the given name exists.
...
Example usage:
$some_variable: 5;
#if variable-exists(some_variable) {
/* I get output to the CSS file */
}
#if variable-exists(nonexistent_variable) {
/* But I don't */
}
For Sass 3.2.x and earlier (my original answer)
I ran into the same problem today: trying to check if a variable is set, and if so adding a style, using a mixin, etc.
After reading that an isset() function isn't going to be added to sass, I found a simple workaround using the !default keyword:
#mixin my_mixin() {
// Set the variable to false only if it's not already set.
$base-color: false !default;
// Check the guaranteed-existing variable. If it didn't exist
// before calling this mixin/function/whatever, this will
// return false.
#if $base-color {
color: $base-color;
}
}
If false is a valid value for your variable, you can use:
#mixin my_mixin() {
$base-color: null !default;
#if $base-color != null {
color: $base-color;
}
}

If you're looking for the inverse, it's
#if not variable-exists(varname)
To check if it is undefined or falsy:
#if not variable-exists(varname) or not $varname
And if you want to set it only if it's undefined or null
$varname: VALUE !default;

Just as a complementary answer - you should have a look on the default keyword for certain use cases. It gives you the possibility to assign a default value to variables in case they are not defined yet.
You can assign to variables if they aren’t already assigned by adding
the !default flag to the end of the value. This means that if the
variable has already been assigned to, it won’t be re-assigned, but if
it doesn’t have a value yet, it will be given one.
Example:
In specific-variables.scss you have:
$brand: "My Awesome Brand";
In default-variables.scss you have:
$brand: company-name !default;
$brand-color: #0074BE !default;
Your project is built like this:
#import "specific-variables.scss";
#import "default-variables.scss";
#import "style.scss";
The value of brand will be My Awesome Brand and the value of brand color will be #0074BE.

Related

How to deprecate Sass pure variables?

I have some Sass variables that I want to deprecate soon. How do I do it, without changing the implementation of it?
I want to allow the usage of variables below, but I want to show a message during the compilation step (whenever they are used), that the variables below are going to be deprecated. Is it possible? Also, can I display where the deprecated variable was used?
$screen-xs-min: 320px;
$screen-sm-min: 480px;
$screen-md-min: 768px;
$screen-lg-min: 992px;
$screen-xl-min: 1200px;
$screen-xs-max: ($screen-sm-min - 1);
$screen-sm-max: ($screen-md-min - 1);
$screen-md-max: ($screen-lg-min - 1);
$screen-lg-max: ($screen-xl-min - 1);
I was looking for the solution for this as well and created my own. Maybe this will help someone else.
#use "sass:list";
// The key is the deprecated variable, note the missing $. The value is the newly suggested one.
$deprecated: (
"font-size-h1": "$font-size-h1-clamp",
"font-size-h2": "$font-size-h2-clamp",
"font-size-h3": "$font-size-h3-clamp",
"font-size-h4": "$font-size-h4-clamp",
"font-size-medium": "$font-size-medium-clamp",
"font-size-large": "$font-size-large-clamp",
"font-size-extra-large": "$font-size-extra-large-clamp",
);
#each $variable in $deprecated {
#if variable-exists(list.nth($variable,1)) {
#warn "The scss variable $#{list.nth($variable,1)} is deprecated. Please use #{list.nth($variable,2)} instead.";
}
}

Check if SASS Map exists

How do I check if a SASS Map already exists and only define one if this is not the case?
I have tried:
#if ($myMap) {
// do someting
}
and
#if variable-exists($myMap) {
// do something
}
But I get the error "Undefined variable: "$myMap"?
I'm sure this is pretty straghtforward but I can't seem to find the answer online?
Thanks
It's a little confusing, but when checking for a variable's existence, skip the $. You also need to set it as a global variable so it doesn't get scoped only to the #if block. This works:
#if variable-exists(myMap) == false {
$myMap: (
1: "foo",
2: "bar"
) !global;
}
// ... now you can use your variable
You can set the map to exist as null be default, which means that if it isn't explicitly set anywhere, then the variable still exists but with a null value. You can then check if the value of the variable is null using an if statement.
$myMap: null !default;
#if $myMap == null {
}

susy argument from sass custom function

How can I pass the return value form a custom Sass function to a Susy function?
Or is there any better approach?
This works fine:
.foo{
max-width: get_breakpoint('large');
}
But that won't:
.foo{
#include layout(get_breakpoint('large') 12);
}
Susy just falls back to the default container width instead of using the one from my get_breakpoint() function.
The built uses Compass, I have the following function in my config.rb:
module Sass::Script::Functions
#
# get breakpoint values from prefs file (json)
#
def get_breakpoint(bp)
if PROJ_PREFS['breakpoint'].include?(bp.value)
retVal = PROJ_PREFS['breakpoint'][bp.value][0].to_s+'px'
else
retVal = bp.to_s
end
Sass::Script::String.new(retVal)
end
end
Software versions: sass (3.4.21), compass (1.0.3), susy (2.2.12).
Many thanks.
It turns out that it shouldn't be a problem to use a custom function as a Susy mixin argument as long as it passes the right value. I was passing a string instead of Sass number.
Just in case someone stumble across similar problem, below there is an example of working solution retrieving breakpoint values from Json into Sass (Assuming you've got json gem installed).
Note that this solution isn't perfect from the performance point of view as it recreates the $BREAKPOINT map each time the _base.scss partial is imported. (It also omits my custom breakpoint mixin as not relevant here and which uses the breakpoint function as well)
My breakpoint definitions are stored as 'unitless' numbers in json
{
"breakpoint" : {
"mini" : [ 481 , "phablet portrait phone landscape"],
"xsmall" : [ 736 , "phablet landscape (iPhone6plus) tablet portrait"],
...
Ruby code (in Compass config.rb)
require 'json'
file = File.read(File.dirname(__FILE__)+'/preferences.json')
PROJ_PREFS = JSON.parse(file)
module Sass::Script::Functions
def get_breakpoints()
retMap = Hash.new
PROJ_PREFS['breakpoint'].each do |bp_name, bp_value|
retMap[Sass::Script::Value::String.new(bp_name.to_s)] = Sass::Script::Value::Number.new(bp_value[0],'px')
end
Sass::Script::Value::Map.new(retMap)
end
end
Sass code (e.g. _base.scss)
// create Sass map with custom function
$BREAKPOINT:get_breakpoints();
// allow Sass numbers (such as 12em, 355px) or breakpoint names (such as small, large) to be passed through
// it was just easier for me to code it in Sass (since I don't know Ruby at all)
#function breakpoint($key) {
#if not map-has-key($BREAKPOINT, $key) { #return $key; }
#return map-get($BREAKPOINT, $key);
}
Usage example (involving Susy)
.foo{
#include container(breakpoint(large));
}

How do I access a specific element of a mapping?

I have the following mapping to contain all of the colours from my theme:
$_base-ocean:rgb(13,176,184);
$_base-hover:10%;
$themes: (
ocean: (
base: $_base-ocean,
hover: darken($_base-ocean, $_base-hover)
)
);
I know how to use an #each loop to get the key/value information from a mapping, but how can I directly access the value of a mapping without using a loop? I tried using square brackets like you would in other languages like JavaScript:
#each $name, $colors in $themes {
[data-page="home"] {
#slider-pagers{
a.#{$name} {
background-color: $colors[base]; // <- line 21
}
}
}
}
But I get a syntax error instead:
error sass/test.scss (Line 21: Invalid CSS after "...d-color: $color": expected ";", was "[base];")
You have the use the map-get function. Sass does not provide a special syntax for accessing values of a mapping.
#each $name, $colors in $themes {
[data-page="home"] {
#slider-pagers{
a.#{$name} {
background-color: map-get($colors, base);
}
}
}
}
Now you get the following output:
[data-page="home"] #slider-pagers a.ocean {
background-color: #0db0b8;
}
A good practice when using SassScript maps (not "source maps"; those are different) is to always quote the keys. For example:
$site-global: (
"ocean": (
"base": $_base-ocean,
"hover": darken($_base-ocean, $_base-hover)
)
);
In order to be compatible with CSS, Sass interprets some unquoted identifiers (including ocean) as color names and translates them internally to color values. When emitting compressed output, Sass will try to produce the smallest possible representation of those colors, which in this case is a hex code. Quoting the keys makes it clear that they should always be strings and should always be emitted with their string values.
Fixed Issue 1 !
It was a case of doing another map-get within a map-get. Once done, I had to reference the default value (this being base). As long as all my default values had a base value this seems to work fine:
#each $theme-colour, $color in $site-global {
[data-page="home"]{
#slider-pagers a.#{$theme-colour}{
background-color:map-get(map-get($site-global, $theme-colour), base);
}
}
}
How it still fails to minify when this code:
#{$variable_here}
Funny enough this code does not:
#slider-pagers a #{$theme-colour}{
or
#slider-pagers a#{$theme-colour}{
Thanks for responding, if anyone knows the is compiling issue that would be great.

How can I use SASS #for loops to pass values to a Breakpoint include? [duplicate]

This question already has an answer here:
Use array value as variable [duplicate]
(1 answer)
Closed 8 years ago.
I am trying to set up a loop that will create a set of classes, using another loop that I've set up, for each of the breakpoints I've defined. These are the BP's
$mobileMedium : 480px;
$tabletVertical : 768px;
$desktopNormal : 1024px;
$desktopWide : 1200px;
And this is my scss.
#each $bpName in "mobileMedium", "tabletVertical", "desktopNormal", "desktopWide"{
#include breakpoint($$bpName) {
#include generate("$bpName", "Container", "width");
}
}
I was hoping for something like this (example):
#media (min-width: 768px) {
.tabletVerticalContainer-1_12 {
width: 8.33333%;
}
But this is what Terminal returns:
error sass/screen.scss (Line 36 of sass/_grid.scss:
Invalid CSS after "...ude breakpoint(": expected ")", was "$$bpName) {")
I guess it boils down to how I can combine the both text (the '$') and the variable in the argument.
Any ideas?
You can't do variable name interpolation, although Sass 3.3 will introduce a map feature that you'll be able to use to get your desired effect (see https://github.com/nex3/sass/issues/642).
For now, instead of iterating over a list of strings, try iterating over a list of lists of string/variable pairs:
#each $breakpoint in "mobileMedium" $mobileMedium, "tabletVertical" $tabletVertical, "desktopNormal" $desktopNormal, "desktopWide" $desktopWide {
#include breakpoint(nth($breakpoint, 2)) {
#include generate(nth($breakpoint, 1), "Container", "width");
}
}
(I don't know what the generate mixin does, I assume from your example that you want the first argument to be a string though.)

Resources