LESS Repeat selector declaration inside a mixin - css

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');
}

Related

If statements in SCSS using CSS variables

My JS code sets a variable on the page:
if (error) {
document.querySelector('body')?.style.setProperty('--pickererror', `'${ error }'`);
}
My SCSS code uses that variable for some content:
$error: var(--pickererror);
// ERROR MESSAGE
.picker-toolbar::before {
content: $error;
color: red;
padding: 5px 35px 0px 35px;
text-align: center;
}
.picker-toolbar {
#if str-length($error) > 0 {
order: 1 !important;
}
}
The ::before section works completely. However the #if clause executes even if there is no error, and the .picker-toolbar is always at order: 1.
I have checked that --pickererror is not present when there's no JS error. I've tried any number of permutations, such as
Putting the #if line before the .picker-toolbar line
Simply using #if $error
Using #if var(--pickererror) in place of the $error variable.
How do I make this work?
What if you remove the logic from your stylesheet entirely, and instead rely on the dynamic property value from your JavaScript?
For example:
body {
--pickererror: 0; // Default value for the "order" property
}
If your JavaScript detects an error, the --pickererror is given a value of 1 !important;
if (error) {
document.querySelector('body').style.setProperty('--pickererror', '1', 'important');
}
And in your stylesheet, you only need a single rule that changes the order if the JavaScript says so.
.picker-toolbar {
order: var(--pickererror, 0); // Defaults to "0" also if the variable doesn't exist
}
I realize this only makes sense if you are using --pickererror mostly as a boolean (I thought since you're checking if its length is greater than 0). If you're actually using the string value, it would be better to create an --error-order variable for this single purpose I guess.

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.

for loop not working in less css mixin

I have noticed that a piece of Less I thought was working as expected is not actually generating all the styles I need - my for loop is not working.
The less in question is:
.for(#list, #code) {
& {
.loop(#i:1) when (#i =< length(#list)) {
#value: extract(#list, #i);
#code();
.loop(#i + 1);
}
.loop();
}
}
.role-variants(#variants, #props){
.for(#variants, {
.security_class_#{value} {
#props();
}
});
}
#admin-roles: admin, admin_manager, admin_user, admin_manager_user;
html{
body{
&.admin{
.role-variants(#admin-roles, {display: block;});
}
}
}
on http://less2css.org/ this compiles correctly, generating the classes I expect.
When I compile locally, I only get the following class:
html body.admin .security_class_admin {
display: block
}
My for loop is not working locally, though it seems to be valid and working using the less compiler. Any ideas on how I can modify it to work locally, or perhaps I need to update my environment to a specific version, though it seems to be up to date.
Thanks again for your help.
Jamie
my loop was malformed - this loop now works as expected:
.for(#list, #code) {
& {
.loop(#i) when (#i > 0) {
#value: extract(#list, #i);
#code();
.loop((#i - 1));
}
.loop(length(#list));
}
}

& as sibling for mixin in LESS

I built a mixin to handle icon sprites in my solution, which basically loops through a list of class names and sets the background position relative to it's index in the sprite
#icon_size-small: 16;
#icon_size-medium: 24;
#icon_size-large: 32;
.commonIcons(#iconSize, #y: 1, #x: 0) {
#posY: (#iconSize * #y);
#posX: (#iconSize * #x);
background: url('images/icons/commonIcons#{iconSize}x#{iconSize}.png') -#posX + 0px -#posY + 0px no-repeat;
height: #iconSize + 0px;
width: #iconSize + 0px;
}
I then call this mixin inside of another one like this
.icons_list-small(#modifier, #x) {
.icon-clock { .commonIcons(#icon_size-small, 1, #x); }
.icon-checkmark { .commonIcons(#icon_size-small, 2, #x); }
.icon-stop { .commonIcons(#icon_size-small, 3, #x); }
etc
and the whole thing is then actually used like this
.small-button {
.icons_list-small(0);
}
So the background position is calculated based on which .icons_list-xxx I use, and the parameter I'm sending in in .small-button decides which y-index is shown (the sprite has 3 variants in a row).
This all works fine when generated as children inside of .small-button, but I've now run up against a case where I need the list generated as sibling selectors to .small-button (giving me .small-button.icon-clock { })
Implementing it like these examples gives me parse errors, understandably:
.small-button.icons_list-small(0);
or
.small-button {
&.icons_list-small(0);
}
So the question: does anyone have a suggestion for what I can do in this instance?
Thanks for any help guys!
Edit: I found a fix myself, but if anyone has a more elegant solution I'd be happy to hear it!
What I did was extend the .icons_list-small mixin like this
.icons_list-small(#modifier, #x) {
#{modifier}.icon -clock { .commonIcons(#icon_size-small, 1, #x); }
Which is then called like this
.icons_list-small(~".icon--inverted", 0);
One solution would be to use the & in your mixin:
.icons_list-small(#x) {
&.icon-clock {
.commonIcons(#icon_size-small, 1, #x);
}
&.icon-checkmark {
.commonIcons(#icon_size-small, 2, #x);
}
&.icon-stop {
.commonIcons(#icon_size-small, 3, #x);
}
}
And when you want to obtain the previous behaviour, to use:
.small-button {
& * {
.icons_list-small(0);
}
}
Which would generate
.small-button *.icon-clock {...}
...
that is equivalent (in CSS) to
.small-button .icon-clock {...}
...
And using it without the & *:
.small-button {
.icons_list-small(0);
}
will generate:
.small-button.icon-clock {...}
...

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

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

Resources