Ampersand (&) as a variable inside SASS #mixin - css

Looking up Bootstrap 5 code, I faced this following statement on bootstrap/scss/mixins/_forms.scss file:
#mixin form-validation-state-selector($state) {
#if ($state == "valid" or $state == "invalid") {
.was-validated #{if(&, "&", "")}:#{$state},
#{if(&, "&", "")}.is-#{$state} {
#content;
}
} #else {
#{if(&, "&", "")}.is-#{$state} {
#content;
}
}
}
And could not exactly understand the usage of & variable inside the #{if(&, "&", "")} statements. How is it working?
I know the Ampersand (&) character is used, os SASS, to include the parent selector(s), when writing on an indented hierarchy. But I couldn't guess how it has been using on the mentioned statement inside the #mixin, when out of quotes ".
Can someone explaning it?
Thanks in advance!

The reason for this syntax is, that this is some kind of parsing / compatibility issue with dartsass. SASS over the years had a lot of different CLI tools to generate the code and there were somehow different levels of strictness when it comes to errors.
Looks like it is there to prevent an issue. I.e. you should not use & at the root level (there is no root of root - probably a NullPointer or something while compiling) because this will produce the error you see here in the screenshot below:
The reason can be found in the comment above the code you linked:
https://github.com/sass/sass/issues/1873#issuecomment-152293725
Once you add the rule, the error is gone:
Parser used:
https://www.sassmeister.com/

They are using interpolation for part of the selector with the #{if(&, "&", "")}:#{$state} logic.
The if() function works like if(val, res1, res2) where if val is true then res1 is used, else res2, like a ternary. So if the & was truthy e.g. parent selector exists, then it would churn out .was-validated &:valid, using "&", otherwise it would be .was-validated :valid using the empty string.
Here is a CodePen demo which demonstrates the #{if(&, "&", ""} usage.

Related

Should I use a `&` when selecting direct children using `>`?

While writing less, I noticed that the following two snippets:
A.
.parent-el {
& > .direct-child { ... }
}
B.
.parent-el {
> .direct-child { ... }
}
will produce exactly the same css:
.parent-el > .direct-child {
...
}
I have several questions:
Are there any differences between A and B?
Is this intentional and by design?
Which one should I use, and why?
Are there any differences between A and B?
There will be no difference in the compiled CSS. The & in LESS is replaced with the outer selector in compiled CSS. So, A is really the same as doing:
.parent-el {
.parent-el > .direct-child { ... }
}
This, of course, is redundant and defeats the purpose of using LESS in the first place.
Is this intentional and by design?
The & really is not used as I believe it was intended in your example. A good example of using a & in LESS would be something like this:
.parent-el {
// define .parent-el styles
&__child {
// define .parent-el__child styles
}
}
In the above example, the & allows you to shorten the declaration of .parent-el__child.
Which one should I use, and why?
You should use B. In this case, using the & is redundant and unnecessary.
the use of the "&" is optional, when you insert the selector inside another becomes implicit that the intention is to start from your "parent".
Although I get less code when we do not use "&" I prefer to use it because the code is cleaner

How to convert Selenium By object to a CSS selector?

It is very common to locate objects using By in selenium webdriver. I am currently using a ByChained selector and I am wondering is there a way to convert a By object to a CSS selector? For example:
By selector = By.id('something');
String cssSelector = selector.toCSSselector();
// now cssSelector = "#something"
As far as I know, there is no way to convert one locator type to another locator type through code.
You can write any locator (except some XPath, e.g. containing text) as a CSS selector. Just write them all as CSS selectors and that should solve your problem. For example, your id can be located using the CSS selector, "#something". If you need an OR, just add a comma to the CSS selector, e.g. "#someId, #some .cssSelector" is the example from mrfreester's comment. If you have to use XPath for contained text, there is a way to specify ORthere also.
It's a hack, but it works (in most cases). So if you really need to, you can go with something like this:
public String convertToCssSelectorString(By by) {
String byString = by.toString();
if (byString.startsWith("By.id: ")) {
return "#" + byString.replaceFirst("By\\.id: ", "");
} else if (byString.startsWith("By.className: ")) {
return "." + byString.replaceFirst("By\\.className: ", "");
} else if (byString.startsWith("By.cssSelector: ")) {
return byString.replaceFirst("By\\.cssSelector: ", "");
} else {
throw new RuntimeException("Unsupported selector type: " + byString);
}
}
It does not cover all possible selector types but you can add them in the same way. Except for xpath selector, I don't think it would be possible.

Append a variable's value to a selector #if it exists (using variable-exists)

I'm trying to add in a class name to SCSS if it exists.
Using a base framework which outputs 2 CSS files.
theme_1.css
theme_2.css
Each CSS file is compiled from its respective SCSS file - which either contains a variable called $body-container or not.
$body-container: .body-container;
If the variable exists then it should be appended to the body tag.
This is the SCSS that I've tried so far
body #if(variable-exists(body-container)) { $body-container } {
/* styles here */
}
I'm expecting the following:
For the SCSS file that contains the variable
body .body-container {
/* styles here */
}
and without the variable declared
body {
/* styles here */
}
But getting the following error Error: Invalid CSS after "...body-container ": expected ":", was "} {"
As mentioned in my comment, I don't think it is possible to append a variable to a selector in that way (the one used in question). The error message indicates that it expects a : after the variable name which is implying that it is expecting a value assignment to the variable within the #if loop.
Having said that, it is still possible to achieve this using the below workaround. The idea is to see if the variable is defined and if yes, set another dummy variable ($selector) as the concatenation of body and the original variable's ($body-container) value. If not, then just set it as body.
Then we can use this dummy variable ($selector) through interpolation. So, if $body-container has been defined the selector would be body .body-container. Else, it would simply be body.
$body-container: ".body-container";
#if variable-exists(body-container) {
$selector: "body " + $body-container !global;
}
#else {
$selector: "body" !global;
}
#{$selector} {
color: red;
}

how to make case insensivity for css selectors [duplicate]

If I have an HTML element <input type="submit" value="Search" /> a css selector needs to be case-sensitive:
input[value='Search'] matches
input[value='search'] does not match
I need a solution where the case-insensitive approach works too. I am using Selenium 2 and Jquery, so answers for both are welcome.
CSS4 (CSS Selector Level 4) adds support for it:
input[value='search' i]
It's the "i" at the end which does the trick.
Broader adoption started mid-2016: Chrome (since v49), Firefox (from v47?), Opera and some others have it. IE not and Edge since it uses Blink. See “Can I use”...
It now exists in CSS4, see this answer.
Otherwise, for jQuery, you can use...
$(':input[name]').filter(function() {
return this.value.toLowerCase() == 'search';
});
jsFiddle.
You could also make a custom selector...
$.expr[':'].valueCaseInsensitive = function(node, stackIndex, properties){
return node.value.toLowerCase() == properties[3];
};
var searchInputs = $(':input:valueCaseInsensitive("Search")');
jsFiddle.
The custom selector is a bit of overkill if doing this once, but if you need to use it many times in your application, it may be a good idea.
Update
Is it possible to have that kind of custom selector for any attribute?
Sure, check out the following example. It's a little convoluted (syntax such as :input[value:toLowerCase="search"] may have been more intuitive), but it works :)
$.expr[':'].attrCaseInsensitive = function(node, stackIndex, properties){
var args = properties[3].split(',').map(function(arg) {
return arg.replace(/^\s*["']|["']\s*$/g, '');
});
return $(node).attr(args[0]).toLowerCase() == args[1];
};
var searchInputs = $('input:attrCaseInsensitive(value, "search")');
jsFiddle.
You could probably use eval() to make that string an array, but I find doing it this way more comfortable (and you won't accidentally execute any code you place in your selector).
Instead, I am splitting the string on , delimiter, and then stripping whitespace, ' and " either side of each array member. Note that a , inside a quote won't be treated literally. There is no reason one should be required literally, but you could always code against this possibility. I'll leave that up to you. :)
I don't think map() has the best browser support, so you can explictly iterate over the args array or augment the Array object.
input[value='Search'] matches
input[value='search' i] Also matches in latest browsers
Support:
version : Chrome >= 49.0, Firefox (Gecko) >= 47.0, Safari >= 9
You can't do it with selectors alone, try:
$('input').filter(function() {
return $(this).attr('value').toLowerCase() == 'search';
});

Sass: Multiple class selector from a variable

I want to create a variable with classes like so
$multi: foo, bar, baz
And I want to created a combined selector like so:
.foo, .bar, .baz {}
I am used the indented syntax (don't know if that would matter). The reason I want the combined selector generated from the variable: I need to do calculations based on how many classes are defined there. Please don't give a suggestion like (use extends!) because that will require me to make another class. I need to be able to get close or exactly to the regular combined selector output.
I had the same issue as the OP, and this was the first search result I found, so now that I’ve figured it out, I’ll post my solution, even though the question is 1.5 years old.
Here’s what I found out: in order to use a variable as a selector, you use SASS interpolation, which works perfectly with comma-separated strings. However, if you want the variable that holds your selectors to be a list (so that you can use list functions on it, e.g. length($multi)), the interpolation will generate a malformed selector.
So, the simple solution is to first define your variable as a list, then when you need to use it as a selector, convert that list into a comma-separated string:
$multi: ".foo", ".bar", ".baz"
$multi-selector: ""
#each $selector in $multi
#if $multi-selector != ""
$multi-selector: $multi-selector + ", "
$multi-selector: $multi-selector + $selector
#{$multi-selector}
//CSS properties go here
You may want to abstract the list-to-comma-separated-string functionality into a function (note: SASS’s join function doesn’t do this; it joins two lists into a new one). Here’s one possible implementation:
#function unite($list, $glue: ", ")
#if length($list) == 1
#return $list
$string: ""
#each $item in $list
#if $string != ""
$string: $string + $glue
$string: $string + $item
#return $string
At which point the original code can be as concise as:
$multi: ".foo", ".bar", ".baz"
#{unite($multi)}
//CSS properties go here
(code from previous answer) Cool function! Usefull. I just wanted to add some SCSS syntax, to give an opportunity to people to use it:
#function unite($list, $glue: ", ") {
#if length($list) == 1 {
#return $list;
} #else {
$string: "";
#each $item in $list {
#if $string != "" { $string: $string + $glue; }
$string: $string + $item;
}
#return $string;
}
}
At which point the original code can be as concise as:
$multi: ".foo", ".bar", ".baz"
#{unite($multi)}
//CSS properties go here
Compass offers a built-in sprite mixin that does just this. If you don't want to use all of Compass, you could look into their source to see how they are doing it.

Resources