Less CSS - access part of class name and use in mixin - css

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)).

Related

Less to Sass: How to translate less guard when (default()) to sass

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.

LESS loops used to generate column classes in twitter - How do they work?

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.

CSS Pre-processors: Getting the current value of a css property and changing it

Let's say I have following declaration
.foo {
.grid(1);
}
.grid(#num) {
width: ... // Some calculated percentage
}
Now later I realize that I need .foo to be 5% wider is it somehow possible todo .foo { width: +5%;} or something similar?
For LESS
In LESS you cannot get at that info so directly. You would need to modify the .grid mixin to something like:
.grid(#num, #adjust: 0) {
width: (your regular calaculation) + #adjust; // Some calculated percentage
}
By giving it a default of 0, then you can still just pass .grid(1) when you want to, but then if .foo needed an adjustment, you do:
.foo {
.grid(1, 5%);
}
For SASS
I'm not 100% certain, but I believe SASS cannot access the property directly either, and would essentially need to do some type of similar solution where the mixin calculation itself is changed.

LESS Repeat selector declaration inside a mixin

I'm trying to declare background image icons for some table rows:
.i(#file:'file.png', #type) {
&.#{type} {
td:first-child {
background-image: url('../img/#{file}');
}
}
}
I'd like to be able to pass in multiple image types at once:
.i('code.png', 'asp, php, rb, py')
and have it effectively do this:
.i(#file:'file.png', #type) {
&.#{type1},
&.#{type2},
&.#{type3},
&.#{type4}, {
td:first-child {
background-image: url('../img/#{file}');
}
}
}
I know the CSS output will be different, the last code example is for illustration purposes.
Any ideas on how to achieve this, short of just declaring a bunch of empty selectors as placeholders?
Updated for LESS 1.5
This code produces the same effect more efficiently in the later versions of LESS, using the newer extract() and length() functions available in LESS 1.5+. Output will be the same as the original example.
.i(#file:'file.png', #types) {
//find length to make the stop point
#stopIndex: length(#types);
//set up our LESS loop (recursive)
.loopTypes (#index) when (#index =< #stopIndex) {
#class: extract(#types,#index);
//print the CSS
&.#{class} {
td:first-child {
background-image: url('../img/#{file}');
}
}
// next iteration
.loopTypes(#index + 1);
}
// "call" the loopingClass the first time getting first item
.loopTypes (1);
}
.myClass {
.i('code.png'; asp, php, rb, py;);
}
With Loops and Inline-Javascript in LESS 1.3.3
This took a few hours to come up with (no, I didn't have a bunch of free time to work on it, I'm just hopelessly addicted...). One of the parts that took the longest was figuring out why my #stopIndex was not being seen as a number by LESS when I was returning the .length of the array, and throwing a type error. I finally discovered I need to explicitly tell it to see it as a number using the unit() function of LESS.
The solution utilizes general concepts from these sources:
The LESS looping
The Javascript functions in LESS
LESS
.i(#file:'file.png', #type) {
//find length to make the stop point
#stopIndex: unit(`(function(){ return #{type}.split(",").length})()`);
//need to get the first item in #type
#firstClass: ~`(function(){
var clsArray = #{type}.replace(/\s+/g, '').split(",");
return clsArray[0];
})()`;
//set up our LESS loop (recursive)
.loopTypes (#index, #captureClass) when (#index < #stopIndex) {
#nextClass: ~`(function(){
var clsArray = #{type}.replace(/\s+/g, '').split(",");
//don't let it try to access past array length
if(#{index} < (#{stopIndex} - 1)) {
return clsArray[#{index} + 1];
}
else { return '' }
})()`;
//print the CSS
&.#{captureClass} {
td:first-child {
background-image: url('../img/#{file}');
}
}
// next iteration
.loopTypes(#index + 1, #nextClass);
}
// define guard expressoin to end the loop when past length
.loopTypes (#stopIndex, #captureClass) {}
// "call" the loopingClass the first time getting first item
.loopTypes (0, #firstClass);
}
.myClass {
.i('code.png', 'asp, php, rb, py');
}
CSS Output
.myClass.asp td:first-child {
background-image: url('../img/code.png');
}
.myClass.php td:first-child {
background-image: url('../img/code.png');
}
.myClass.rb td:first-child {
background-image: url('../img/code.png');
}
.myClass.py td:first-child {
background-image: url('../img/code.png');
}

SCSS setting a value based upon another

I'm just trying to work out if something is possible or not with SCSS.
Please feel free to ask me for more details if I'm not very clear in what I'm asking, but here's what I'm trying to achieve.
Pseudo code:
.class1 { width:100px; }
.class2 { margin-right:[.class1{width}] + 2 }
compiling into
.class1 {width:100px; }
.class2 { margin-right:102px; }
I believe to get what you want, a variable is best used:
$yourWidth: 100px;
.class1 { width: $yourWidth; }
.class2 { margin-right: ($yourWidth + 2); }
Update (based on comment info)
You might add a global variable below $ColCount that begins as an empty list, like so:
$WidthList: ();
Then inside #mixin columns($numCols) after $colWidth is calculated, add the width value for that column to the $WidthList by adding this function:
join($WidthList, $colWidth);
Then, once all the columns have calculated, you should have a list containing all the width values, so that you can access them if you desire elsewhere, and thus...
.class2 { margin-right: (nth($WidthList, 5) + 2); }
...should yield the .cl-col5 value you want for the margin.
Note that I did not test this. Nor have I actually ever used SASS. I am basing this strictly off the documentation found here and elsewhere on their site.

Resources