I am trying to convert some mixins from less to sass.
I am not very skilled in either of these languages, so I'm not sure at all if I'm doing it right.
The original Less mixins:
.for(#i, #n) {.-each(#i)}
.for(#n) when (isnumber(#n)) {.for(1, #n)}
.for(#i, #n) when not (#i = #n) {
.for((#i + (#n - #i) / abs(#n - #i)), #n);
}
.for(#array) when (default()) {.for-impl_(length(#array))}
.for-impl_(#i) when (#i > 1) {.for-impl_((#i - 1))}
.for-impl_(#i) when (#i > 0) {.-each(extract(#array, #i))}
I have identified three main issues:
1) how do I translate guards in sass? Is it correct to gather mixins with the same number of arguments in one single mixin and write conditional blocks inside it?
2) how does when(default()) works? I've been trying to find a good explanation in the documentation but couldn't find any.
3) is there a function in sass which is equivalent to less extract?
Thank you very much!
First thing first, the mixin that you've given in question is one that was created by seven-phases-max to imitate the for and for-each loops because Less doesn't have built-in functions for them. Unlike Less, Sass already has built-in #for and #each directives to perform looping and so I'd recommend you to not spend time on converting these Less mixins to Sass.
Below are Sass samples for a for and a for-each loop:
For:
.column {
#for $i from 1 through 5 { /* execute the loop 5 times */
&-#{$i} { /* create .column-1 to .column-5 using selector interpolation */
width: 20%;
left: (($i - 1) / 5 * 100%);
}
}
}
For Each:
$country-code-list: US, IN, FR, SG; /* list for iteration */
.flag {
#each $country-code in $country-code-list { /* iterate for each value in the list */
&-#{$country-code} { /* create .flag-US, .flag-IN etc using selector interpolation */
background-image: url(http://yoursite.com/#{$country-code}.png);
}
}
}
You can try out the above samples # SassMeister.com and see the compiled output.
Now coming to your questions,
1) how do I translate guards in sass? Is it correct to gather mixins with the same number of arguments in one single mixin and write conditional blocks inside it?
The Less guards would translate to #if (conditional statements) in Sass. Below is an example:
#mixin list-style($val) {
#if ($val == i) { list-style-type: lower-roman; }
#else if ($val == I) { list-style-type: upper-roman; }
#else { list-style-type: decimal };
}
#demo { #include list-style(2); }
The second part of your question is probably opinion based as each one would have their own way of doing things. I'd personally prefer grouping them inside one mixin and writing conditional blocks.
2) how does when(default()) works?
The default() guard in Less is like the typical else or default: (in switch-case) statements. The mixin with this as guard gets called only when no other mixin with the same name and the same no. of arguments is matched.
For example, have a look at the below code. Here when the value passed as argument to the mixin is anything other than i or I neither of the first two mixin guards are matched and so those mixins do not get executed. In such a case, the third one (the default()) will get executed and set the list style type as decimal.
.mixin-list-style(#val) when (#val = i) { list-style-type: lower-roman; }
.mixin-list-style(#val) when (#val = I) { list-style-type: upper-roman; }
.mixin-list-style(#val) when (default()) { list-style-type: decimal; }
#demo {
.mixin-list-style(1); /* this will result in list-style-type: decimal */
}
3) is there a function in sass which is equivalent to less extract?
Yes, there is a nth() function in Sass which is equivalent to the Less extract() function. Below is a sample:
$country-code-list: US, IN, FR, SG;
.flag-FR {
$country-code: nth($country-code-list, 3); /* extract 3rd item from country-code-list */
background-image: url(http://yoursite.com/#{$country-code}.png);
}
Note: You don't need this with #each loop because Sass automatically extracts the item and assigns to the variable for each iteration.
Related
Is there any way to customize the variables in SASS?
For example:
.m-b-{$number} {
margin-bottom: $number;
}
If I give class="m-b-50" to an element, it should take margin-bottom 50. I just want to know if it is possible with SASS.
Yes it is possible with the help of variable interpolation or variable substitution which uses #{} for variable substitution in SASS and mixins which is a block of code just like function.
Interpolation is the process of evaluating an expression or a string containing one or more variables, yielding a result in which the variables are replaced with their corresponding values.
Simple example of interpolation and set values to the css property in SASS:
$number:60;
$n: 20px;
.m-b-#{$number}{
margin-bottom: #{$number}px;
margin-top: $n;
}
To create customize class names, will use mixins:
#mixin margin-class($side, $number) {
$firstLetter: str-slice($side, 0, 1);
.m-#{$firstLetter}-#{$number}{
margin-#{$side}: #{$number}px;
}
}
$margins: (10, 20);
$sides: ("top", "right", "bottom", "left");
#mixin generate-margin(){
#each $margin in $margins{
#each $side in $sides{
#include margin-class($side, $margin);
}
}
}
#include generate-margin();
Here, generate-margin() will get executed which will call margin-class() for each $margins and $sides, and will generate the below CSS classes:
.m-t-10 {
margin-top: 10px;
}
.m-r-10 {
margin-right: 10px;
}
.m-b-10 {
margin-bottom: 10px;
}
.m-l-10 {
margin-left: 10px;
}
.m-t-20 {
margin-top: 20px;
}
.m-r-20 {
margin-right: 20px;
}
.m-b-20 {
margin-bottom: 20px;
}
.m-l-20 {
margin-left: 20px;
}
That's the one way when you want only for specific values, but if you want to create margin class for 0-20, you can loop thru 0 to 20 as shown below:
#mixin generate-margin(){
#for $margin from 1 through 20{
#each $side in $sides{
#include margin-class($side, $margin);
}
}
}
For anyone else facing this issue, here is how one can achieve this:-
#for $i from 1 through 10 {
.mb-#{$i} {
margin-bottom: #{$i}rem;
}
}
The answer is: no it is not possible. SASS is just a language to pre-generate CSS for you. There is no on-demand, dynamic creation of classes triggered by the contents of your HTML markup. When it comes time for the browser to render your HTML and apply your specified classes, it is still just using CSS. I.e. if you assign class="m-b-50" to an element, the class .m-b-50 must already be explicitly defined somewhere. As noted in the other answers, SASS can make it easier to generate a bunch of pre-defined classes but you must know which values you want to support up front.
Now, you could generate classes for some very large, all-inclusive range like -1000 to 1000 to effectively support all values you might ever try to use and it would seem to do what you wanted, but you would be forcing your users to download a larger CSS file with, most likely, a large percentage of it being unused CSS which is wasteful and can be inconsiderate in a world of paid & limited data plans.
I am writing a mixin that I want to output a CSS counter. Here's my goal:
.selector:nth-child(3n+1) {
color: green;
}
That makes every three elements, starting with the first, green.
I would like my mixin to take that first number, in my example 3 and output the CSS. Something like this:
#mixin counter($number) {
&:nth-child($number * n + 1) {
color: green;
}
}
Is this possible in Sass at this point?
To use a variable's value in the selector, you should use the interpolation syntax #{} and append the n to it like in the below code block.
#mixin counter($number) {
&:nth-child(#{$number}n + 1) {
color: green;
}
}
.selector{
#include counter(3);
}
The above code compiles successfully and produces the required output when tested using the online compiler at sassmeister.com.
Bootstrap uses some LESS mixins to generate it's column classes (and several other classes);
.make-grid-columns() {
// Common styles for all sizes of grid columns, widths 1-12
.col(#index) when (#index = 1) { // initial
#item: ~".col-xs-#{index}, .col-sm-#{index}, .col-md-#{index}, .col-lg-#{index}";
.col((#index + 1), #item);
}
.col(#index, #list) when (#index =< #grid-columns) { // general; "=<" isn't a typo
#item: ~".col-xs-#{index}, .col-sm-#{index}, .col-md-#{index}, .col-lg-#{index}";
.col((#index + 1), ~"#{list}, #{item}");
}
.col(#index, #list) when (#index > #grid-columns) { // terminal
#{list} {
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (#grid-gutter-width / 2);
padding-right: (#grid-gutter-width / 2);
}
}
.col(1); // kickstart it
}
I can see that LESS mixin guards are being used to create loops, and I can understand the code examples that are given in the LESS documentation;
.loop(#counter) when (#counter > 0) {
.loop((#counter - 1)); // next iteration
width: (10px * #counter); // code for each iteration
}
div {
.loop(5); // launch the loop
}
But I can't seem to grok exactly how the more complex nested guard expressions that bootstrap uses are working. Could somebody comment the above bootstrap code in a bit more detail to give me an indication of what is going on?
The purpose of the .make-grid-columns() mixin is to generate a long list of selectors which all share the same properties.
This list can not be hard code in the code because of the number of columns (#grid-columns) may vary.
You already illustrated the basics of a loop in Less in the question yourself.
To understand the mixins you will have to understand Less allows you the use the same mixin name many times.
Every 'matching' mixins will be compiled into the CSS code.
Mixin guards when () enable you to set a condition for the match. When the guard not match the mixin is not compiled.
Beside mixins guards you can also use pattern matching, than you can match on value as follows:
.mixin1(a,#width){}
.mixin1(b,#width){}
The .mixin(a,20px); call match only the first mixin. Partern matching based on arity (the number of arguments) will
also works. Notice that .col(#index) when (#index = 1) does not need a guard (see also).
the .col(#index) call has only one argument, so the .col(1); only matches that mixin based on arity matching.
The .col(#index) mixin calls the .col(#index, #list) mixin(s). The .col(#index) when (#index = 1) mixin will be only called for the first iteration. The reason for having two mixins in stead of one is that Less don't support if / else. The list of selectors can not start or end with a comma and so the first or last item in the list of selectors should differ from the others.
Alternatively you can use a mixin with an additional argument:
.mixin(#iterator; #item:~""; #seperator:~"") when (#iterator < 5){
#list: ~"#{item} #{seperator} #{iterator}";
.mixin((#iterator + 1); #list; ",");
}
.mixin(#iterator; #list; #seperator) when (#iterator = 5){
.selector{
#{list}: value;
}
}
.mixin(1);
The #seperator will be empty (~"") for the first call and a comma (",") for all other calls.
Notice that a mixin with default parameters also match calls which not having set values for the defaults:
So .call(1); matches the .call(#a; #b:4; #c:5){} mixin.
As already mentioned in the comments ~"#{list}, #{item}" generates the selector list via string concatenation.
The last iteration of .col(#index, #list) when (#index =< #grid-columns) calls col((#grid-columns + 1)....) mixin when #index=#grid-columns and so matches the last .col(#index, #list) when (#index > #grid-columns) mixin in the structure.
#{list} { } use selector interpolation to set the list of selectors and his properties.
Of course you should also read the excellent blog post of #seven-phases-max about this structure to generate a list of selectors.
Finally you should know that Bootstrap needs such a long list of selectors because of it avoid (partial) attribute selectors. In stead of the selector list you can also use the following CSS / Less code:
[class^="col-"], [class*=" col-"]
{
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (#grid-gutter-width / 2);
padding-right: (#grid-gutter-width / 2);
}
The reason to avoid attribute selectors is that some browsers compute them slow. As can be seen at http://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/, you can discuse this argument. Personally i think that unused code is a more important performance issue than the use of attribute selectors in most Bootstrap projects.
I have an error in my sass file, I've got no idea what it's not working...
Here's the message I get from calling .class{ col(6);
error scss/components.scss (Line 18: Invalid CSS after "...gin:7px 0; col": expected "{", was "(6);")
Here's the function and variables used to create the function (sorry if it's a bit confusing):
$columnWidth:40;
$numberOfColumns:16;
$gutterWidth: 20;
$fullWidth:($columnWidth * $numberOfColumns) + ($gutterWidth * $numberOfColumns);
#function perc($target) {
#return (($target / $fullWidth) * 100%);
}
#function gw($n, $fluid: false) {
$calculatedValue: ($n * $columnWidth + ($n - 1) * $gutterWidth);
#if $fluid == false {
#return $calculatedValue + px;
} #else {
#return perc($calculatedValue);
}
}
#function col($n, $fluid: false){
#return unquote("width: gw($n, $fluid);");
}
All im trying to do, is re use the gw() function, so that I can use it in css to output the number of columns as a width css property, ie grid(4); would output width: 200px;.
the function gw works because it generates my grid css properly, however I want to define a global function to use everywhere. Hence the col() function.
A mixin is like a function, but does not return anything other than its body, which can be full blown SCSS.
So you can fix this by making col a mixin, like so:
#mixin col($n, $fluid: false){
width: gw($n, $fluid);
}
Then you can call:
.class{ #include col(6) };
Which produces:
.class {
width: 340px; }
Functions can only be used to return simple types (string, color, number, etc.) or lists of simple types. It cannot be used to return a property/value (eg. "color: red"): all you can ever get when you do this is a string or a list of strings (and strings cannot be used this way). Mucking around with lists of strings is more work for you than simply using a mixin.
#function foo() {
#return color red;
}
.foo {
$kv: foo();
#{nth($foo, 1)}: #{nth($foo, 2)}
}
Compare to:
#mixin foo() {
color: red;
}
.foo {
#include foo;
}
There's nothing inherently non-reusable about your gw() function (in fact, it is more reusable than the impossible function you're dreaming of): it returns a numeric value that can be used for any property. In fact, using it in place of a mixin or a function that returns a list of strings is the most efficient way of going about what you're looking for:
.foo {
width: gw(1);
}
In Less, is it possible to access part of a class name and use within a mixin?
This will be best explained with an example:
I have a grid which i have declared as follows:
.columns (#columns) {
//code here to generate column widths
}
//This is where my problem is:
.column-1 {
.col (1)
}
.column-2 {
.col (2)
}
.column-3 {
.col (3)
}
// etc etc
Obviously there is a lot of repetitive code going on here. Ideally i would like to be able to not have to declare column-1 column-2 etc and have some way, regex perhaps, of parsing the class name, and using the value after the dash to automatically calculate the column width. I am almost certain twitter bootstrap is doing something similar but i cant understand it:
.spanX (#index) when (#index > 0) {
(~".span#{index}") { .span(#index); }
.spanX(#index - 1);
}
.spanX (0) {}
I think you'll understand that :
.columnX (#index) when (#index > 0) { // Guarded mixin: use this mixin when the condition is true (like an if statement)
(~".column-#{index}") { // outputs .column-0 as a class name
.col(#index); // for the contents, use the col mixin
} // which class you are doing
.columnX(#index - 1); // Recursive call to the same mixin, going down by 1
}
.columnX (0) {} // Default implementation so that when .columnX(0) is called, a matching mixin is found.
.col (#index) {
// actual css that will be applied to column-1 if #index is 1
width: #index * 10px; // for example
}
.columnX(3); // number of columns you want
Edit (missed the ; of .columnX(3); )
Edit Added more comments
All this should give the result :
.column-3 {
width: 30px;
}
.column-2 {
width: 20px;
}
.column-1 {
width: 10px;
}
This is essentially the same as #sherbrow's answer, but a little more concise and doesn't error. Consider this a long explanatory comment to support his correct answer - it's just a lot more than fits in a comment!
You'll use a LESS loop mixin like this as an intermediate helper, and then call it specifying the number of classes you want to generate. Here's how to do .column-1, .column-2, and .column-3. If say you wanted to go up to four columns: just do .columns(4) instead of .columns(3) [line 9]. To go up to five columns, just do .columns(5).
1 // we'll call this if we want to build columns
2 .columns(#i) when (#i > 0) {
3 .column-#{i} {
4 .col(#i)
5 }
6 .columns(#i - 1)
7 }
8 // for example, here we build columns 1-3
9 .columns(3);
which will compile to the result of
.column-1 {.col(1)}
.column-2 {.col(2)}
.column-3 {.col(3)}
(Your question assumes there's already a mixin .col(#x) so I'm assuming that too. See 4 for how to skip that extra step.)
Here's what's happening:
The whole first chunk [lines 1-7] just sits there until called.
.columns(3) [line 9] sends us to the .columns(#i) mixin [line 2], assigning the variable #i the value 3.
Because #i (3) is greater than 0 [line 2], we satisfy the guard and are allowed past the { [line 2].
.column-#{i} {...} [lines 3-5] is what this mixin will output.
#i is 3, so the output will be .column-3 {.col(3)}
the syntax #{i} is used to insert the variable's value as a string
If you don't need to use .col(#x) anywhere else, you could also just drop styles in here directly, e.g. (a là #sherbrow) .column-#{i} {width: #i * 10px}
And then the loop: after compiling lines 3-5, call this mixin again but with a different value [line 6]: .columns(#i - 1) ==> .columns(3 - 1) ==> .columns(2).
Back to the top [line 2]: #i, now 2, is greater than zero, so we're allowed in. output .column-2 {.col(2)} (where .col(2) is immediately compiled, so your compiled CSS actually reads .column-2 { the.col(2)styles })
And keep outputting and looping until #i isn't greater than 0 (i.e. stop after .columns(1)).