Function fails to return a value [duplicate] - function

This question already has an answer here:
Sass error "Function X finished without #return"
(1 answer)
Closed 7 years ago.
I am using a sass snippet from this website to create color stacks.
$color-stack:
(group: foo, id: normal, color: #e67835),
(group: foo, id: pale, color: #f8a878),
(group: foo, id: dark, color: #ad490c),
(group: bar, id: normal, color: #426682);
// Color Function
#function color($group, $shade:normal, $transparency:1){
#each $color in $color-stack{
$c-group: map-get($color, group);
$c-shade: map-get($color, id);
#if($group == map-get($color, group) and $shade == map-get($color, id)){
#return rgba(map-get($color, color), $transparency);
}
}
}
Later in my code I want to use #each to give some elements different colors depending on their parent class
#each $category in foo, bar {
.cat-#{$category} {
.some-class {
background-color: color(#{$category}, pale);
}
}
}
I would expect this to compile to:
.cat-foo .some-class {
background-color: #f8a878; //the value of foo pale on the $color-stack map
}
Instead it throws and error: Function color did not return a value
If I replace the #{$category} with the string foo it works as expected.

This is because you are looking for a group with the value bar and with an id of pale. This does not exist in your map so the function does not return a value.
Add that and it works.
$color-stack:
(group: foo, id: normal, color: #e67835),
(group: foo, id: pale, color: #f8a878),
(group: foo, id: dark, color: #ad490c),
(group: bar, id: normal, color: #426682),
(group: bar, id: pale, color: #000);

Related

Cutting down repeating code in Sass functions

I'm working on an HTML boilerplate that uses a lot of Sass to create a maintainable and easy to use code base. Part of that are a few functions:
// Returns the scale value found at $key.
#function get-type-scale($key) {
$value: map-get($type-scale, $key);
#if $value != null {
#return $value;
}
#else {
#warn "Unfortunately, `#{$key}` is not defined in the `$type-scale` map.";
}
}
// Returns the line-height value found at $key.
#function get-line-height($key) {
$value: map-get($line-heights, $key);
#if $value != null {
#return $value;
}
#else {
#warn "Unfortunately, `#{$key}` is not defined in the `$line-heights` map.";
}
}
// etc... I have 2 more functions like this where only the $map changes.
These functions then get called in a few mixins, like this:
// Sets font-size and line-height based on the $level.
#mixin set-type($level: 0) {
font-size: get-type-scale($level);
line-height: get-line-height($level);
}
While this works just fine, I don't like the fact that I'm repeating a lot of code in the functions. I've tried writing a generic function that receives a map name as a parameter, but I cannot use interpolation in map-get().
Is there a way to make the function code more elegant and as DRY as possible?
I appreciate any insights. Cheers!
I've tried writing a generic function that receives a map name as a parameter, but I cannot use interpolation in map-get().
Unfortunately, it is not possible to create variable names from the names of other variables at all (only from its values). Furthermore, a variable only knows its value(s) and not its name which is another issue we are facing when it comes to define the variable warning messages.
I come up with a little improvement that reduces the amount of duplicated code and keeps the ease of use of the high-level function calls. Criticism of having to pass a third variable to the generic function is justified, but I simply couldn't find a clean way to avoid it.
$line-heights: (
0: 1em,
1: 2em
);
$type-scale: (
0: 1em,
1: 2em
);
// Returns the scale value found at $key.
#function get-type-scale($key) {
#return get-value-or-warn($type-scale, $key, 'type-scale');
}
// Returns the line-height value found at $key.
#function get-line-height($key) {
#return get-value-or-warn($line-heights, $key, 'line-heights');
}
#function get-value-or-warn($map, $key, $map-name) {
$value: map-get($map, $key);
#if $value != null {
#return $value;
}
#else {
#warn "Unfortunately, `#{$key}` is not defined in the `$#{$map-name}` map.";
}
}
// Sets font-size and line-height based on the $level.
#mixin set-type($level: 0) {
font-size: get-type-scale($level);
line-height: get-line-height($level);
}
You could create line-height and font-size as key/value pairs in a nested map and use interpolation to print out the property in an #each loop – like:
$map:(
0 : (line-height: 1.3, font-size: 16px),
1 : (line-height: 1.3, font-size: 17px),
2 : (line-height: 1.3, font-size: 18px),
3 : (line-height: 1.4, font-size: 19px),
4 : (line-height: 1.5, font-size: 20px)
);
#mixin set-type($level: 0){
$found: map-get($map, $level);
#if not $found { #warn 'Key `#{$level}` not found in $map!'; }
#else { #each $prop, $value in $found { #{$prop} : $value; } }
}
.class { #include set-type(1); } // line-height: 1.3; font-size: 17px;
.class { #include set-type(5); } // WARN: Key `5` not found in $map!

SCSS: Send Attribute and Value to Function

Note:
I'm new to web development and object oriented programming. I am brand new to SCSS and haven't yet grasped a solid understanding of the syntax. I have a basic understanding of how to use functions in SCSS.
Let me start off by defining the result I want to achieve.
_body.scss
body {
background-color: red;
}
Now I know if I wanted to obtain this result in Javascript I could:
Option 1: write a string of HTML code and replace the existing html tag.
Not going to code this, as this is a messy way of writing Javascript, but essentially using document.write() method.
Option 2: use the "setAttribute()" method
// assuming <head> and <body> are the only tags within <html>
var bodyTag = document.firstElementChild.lastElementChild;
bodyTag.setAttribute( "bgcolor", "red" );
I know there are additional ways to do this in Javascript, but for this example, I will focus on these two.
So I want to create a SCSS function that can return both the attribute and the value.
_body.scss ( Pseudocode string example )
#function makeAttribute( $attribute, $value )
{
#return $attribute + ":" + $value + ";";
}
body {
makeAttribute( background-color, red );
}
I have yet to find a built in function that addresses this ( similar to the "setAttribute()" method in Javascript ), or the string example above.
I know that functions can take: number, string, bool, color, list, map or null; but what I don't know is if an attribute fits into any of these value types ( for instance: string ).
I feel as if the article: Bringing Configuration Objects To Sass may be explaining what I am trying to do, but I'm having difficulty understanding this article ( so it may not be an explanation to a solution ).
My end goal is to create a function that would write the following css. I did not mention the browser support previously as it adds another layer of complexity that may or may not be easily explained.
body {
background-color: red;
-o-background-color: red;
-ms-background-color: red;
-moz-background-color: red;
-webkit-background-color: red;
}
i don't know if this have to be a function, i found it more logic use a mixin instead:
// Option 1
#mixin makeRule($value: red, $property: background-color) {
#{$property}: $value;
}
// Option 2:
#mixin makeRuleWithPrefixes($value: red, $property: background-color) {
#{-ms- + $property}: $value;
#{-o- + $property}: $value;
#{-moz- + $property}: $value;
#{-webkit- + $property}: $value;
#{$property}: $value;
}
/////////
body {
#include makeRule;
}
article {
#include makeRule(black);
}
p {
#include makeRule(2px solid blue, border)
}
span {
#include makeRuleWithPrefixes;
}
i changed the name, because is no right say - makeAttribute, when you are creating a cssRule ( selector + property name + property value ), well this is up to you ;)
ok the first,you need interpolation to use a variable as a property name.
The value is the first argument, so now you can use the default property, and just pass different values ( like the article :) )
or you can now set all the properties you want it, just pass the property as the second value ( like p )
body {
background-color: red;
}
article {
background-color: black;
}
p {
border: 2px solid blue;
}
span {
-ms-background-color: red;
-o-background-color: red;
-moz-background-color: red;
-webkit-background-color: red;
background-color: red;
}
I made the option two, because you ask it but i warn you, this is not a good approach. You could use a build tool ( webpack, gulp, grunt.. whatever ) than use a autoprefixer package that do this prefix automatically, this way is a pain because you have to be updating the #mixin eventually.

Random background image for each class instance

I have a sass function which returns a random url from a given set of urls as follows:
#function randomUrl(){
$images: (
"/images/watermarks/area-watermark.png",
"/images/watermarks/bar-watermark.png",
"/images/watermarks/line-watermark.png",
$img: nth($images, random(length($images)));
#return $img;
}
and i am assigning it to a class as follows:
.myClass{
background-image: url(randomUrl());
}
What i want now is to get a random image FOR EACH class instance, i.e, if i have 10 divs with class "myClass" in my HTML, i want the background images of each div to be different. My approach till now just gives me one random image which appears in all the divs everytime i compile.
The random() function does exactly what it sounds like: it generates a random number between 2 specified numbers. There is no guarantee that the numbers will be different each time the function is called because that's not how random works.
What you need is a way to shuffle your list, but there is no such function to do that in the Sass standard library. There are a couple of 3rd party libraries that do:
https://github.com/at-import/SassyLists (sl-shuffle)
https://github.com/mknadler/randomize.scss (shuffle)
The implementation in both libraries is nearly identical (this one was lifted from randomize.scss):
#function shuffle($list) {
$list-length: length($list);
#while($list-length > 0) {
$rand: random($list-length);
$temp: nth($list, $rand);
$list: set-nth($list, $rand, nth($list, $list-length));
$list: set-nth($list, $list-length, $temp);
$list-length: $list-length - 1;
}
#return $list;
}
If you're intentionally avoiding iterating over a list, you could use it like this:
#import "SassyLists";
$last-pos: 0;
$images: sl-shuffle(
"/images/watermarks/area-watermark.png"
"/images/watermarks/bar-watermark.png"
"/images/watermarks/line-watermark.png");
#function randomUrl(){
$last-pos: if($last-pos == length($images), 1, $last-pos + 1) !global;
#return nth($images, $last-pos);
}
.myClass {
background-image: url(randomUrl());
}
.myClass {
background-image: url(randomUrl());
}
.myClass {
background-image: url(randomUrl());
}
Output:
.myClass {
background-image: url("/images/watermarks/line-watermark.png");
}
.myClass {
background-image: url("/images/watermarks/area-watermark.png");
}
.myClass {
background-image: url("/images/watermarks/bar-watermark.png");
}
Though I recommend just using iteration instead and cut out the use of the function all together:
#import "SassyLists";
$images: sl-shuffle(
"/images/watermarks/area-watermark.png"
"/images/watermarks/bar-watermark.png"
"/images/watermarks/line-watermark.png");
#for $i from 1 through length($images) {
.myClass-#{$i} {
background-image: url(nth($images, $i));
}
}
http://sassmeister.com/gist/d0c65d02be52aa31f836

Is there a formula that I can apply to find out a complementary color?

I have a web page with a blue/violet background and a simple table with a white background and currently a blue hover color. This is demonstrated in this fiddle. Note that I put the example in jsfiddle because it was an easy place to show the HTML for others but I am not looking for a javascript solution. It's just for example purposes.
http://jsfiddle.net/gL7Lpys0/
I would like to find a complementary color for the hover. Is there a formula or a way that I can do this mathemetically? Note that I don't want to set it at run time I would just like to find out if there's a way I can find a good color that would match so I can use this at design time.
Here are the details:
Background body color: #162252
Table header color: #e3e3e3
Table background color: #FFFFFF
Hover row color: #0081c2 << This color currently does not match /
look good so I would like to find out
how I can calculate something that
matches.
Here is how you can compute complementary colors with JavaScript:
// complement
temprgb=thisrgb;
temphsv=RGB2HSV(temprgb);
temphsv.hue=HueShift(temphsv.hue,180.0);
temprgb=HSV2RGB(temphsv);
function RGB2HSV(rgb) {
hsv = new Object();
max=max3(rgb.r,rgb.g,rgb.b);
dif=max-min3(rgb.r,rgb.g,rgb.b);
hsv.saturation=(max==0.0)?0:(100*dif/max);
if (hsv.saturation==0) hsv.hue=0;
else if (rgb.r==max) hsv.hue=60.0*(rgb.g-rgb.b)/dif;
else if (rgb.g==max) hsv.hue=120.0+60.0*(rgb.b-rgb.r)/dif;
else if (rgb.b==max) hsv.hue=240.0+60.0*(rgb.r-rgb.g)/dif;
if (hsv.hue<0.0) hsv.hue+=360.0;
hsv.value=Math.round(max*100/255);
hsv.hue=Math.round(hsv.hue);
hsv.saturation=Math.round(hsv.saturation);
return hsv;
}
// RGB2HSV and HSV2RGB are based on Color Match Remix [http://color.twysted.net/]
// which is based on or copied from ColorMatch 5K [http://colormatch.dk/]
function HSV2RGB(hsv) {
var rgb=new Object();
if (hsv.saturation==0) {
rgb.r=rgb.g=rgb.b=Math.round(hsv.value*2.55);
} else {
hsv.hue/=60;
hsv.saturation/=100;
hsv.value/=100;
i=Math.floor(hsv.hue);
f=hsv.hue-i;
p=hsv.value*(1-hsv.saturation);
q=hsv.value*(1-hsv.saturation*f);
t=hsv.value*(1-hsv.saturation*(1-f));
switch(i) {
case 0: rgb.r=hsv.value; rgb.g=t; rgb.b=p; break;
case 1: rgb.r=q; rgb.g=hsv.value; rgb.b=p; break;
case 2: rgb.r=p; rgb.g=hsv.value; rgb.b=t; break;
case 3: rgb.r=p; rgb.g=q; rgb.b=hsv.value; break;
case 4: rgb.r=t; rgb.g=p; rgb.b=hsv.value; break;
default: rgb.r=hsv.value; rgb.g=p; rgb.b=q;
}
rgb.r=Math.round(rgb.r*255);
rgb.g=Math.round(rgb.g*255);
rgb.b=Math.round(rgb.b*255);
}
return rgb;
}
//Adding HueShift via Jacob (see comments)
HueShift(h,s) {
h+=s; while (h>=360.0) h-=360.0; while (h<0.0) h+=360.0; return h;
}
//min max via Hairgami_Master (see comments)
function min3(a,b,c) {
return (a<b)?((a<c)?a:c):((b<c)?b:c);
}
function max3(a,b,c) {
return (a>b)?((a>c)?a:c):((b>c)?b:c);
}
Source

Can I use a variable for a function name?

I'm trying to loop through a list which automates several functions. Unfortunately the function is not evaluated.
For example:
$colors:
red,
blue,
green;
#each $color in $colors{
.color-#{$color} {
value: $color(#F15258);
}
}
(I've simplified my example code to make it easier to illustrate).
Unfortunately this just outputs the value of $key and the color #F15258.
ie:
value: red #F15258;
Can I get SASS to pass in the variable as the function name so it actually evaluates `red(#F15258)?
It should output:
value: 241;
Any thoughts?
As of Sass 3.3 you can do this using the call() function:
$colors:
'red',
'blue',
'green';
#each $color in $colors{
.color-#{$color} {
value: call($color, #F15258);
}
}
Output:
.color-red {
value: 241;
}
.color-blue {
value: 88;
}
.color-green {
value: 82;
}
Note that your variables must be a string: red is a Color while 'red' is a String.
SASS does not allow dynamic names, and that's a good thing.
To use a dynamic name, you'll have to use a template to generate your SASS prior to compiling it. See how Compass does it: https://stackoverflow.com/a/16129685/901944
This increases the complexity of your project greatly and i strongly advise against.
Instead, use a function that accepts the name as a parameter:
#function parse-color($color) {
// Do whatever you want here
}
.color-red {
color: parse-color(red);
}
Note that instead of hardcoding the second color you can have it as an argument with a default value:
#function parse-color($first-color,
$second-color: #F15258) {
// Do whatever you want here
// For example:
#return mix($first-color, $second-color);
}
$colors:
red,
blue,
green;
#each $color in $colors{
.color-#{$color} {
color: parse-color($color);
}
}
See a demo: http://sassbin.com/gist/6193779/
The variable with multiple values are called as lists in sass.
so,
you can make a list like:-
$colors: red blue green; //list of colors
#each $color in $colors{
.color-#{$color} {
color: ($color);
}
}