Less: how to grab an entire ruleset from within a map - less

I have a nested ruleset (map) like the one below.
#typography: {
#h1: {
font: roboto;
font-weight: 300;
font-size: 9.6rem;
line-height: 9.6rem;
text-transform:none;
}
}
I know how to retrieve and output a single key such as [font], but is there any way of returning and outputting the whole of inner ruleset?
.myclass {
font: roboto;
font-weight: 300;
font-size: 9.6rem;
line-height: 9.6rem;
text-transform:none;
}

"Can't work this way currently (v3.9)".
I'm afraid it's not going to work the way (specifically the map itself) it is.
Intuitively it would be something like:
#usage {
#typography[#h1]();
}
But currently this feature (cascading () and [] operators) is not implemented.
A first-guess workaround like "assign a ruleset of interest to a temporary variable and then 'call' it" also fails:
#usage {
#temp: #typography[#h1];
#temp(); // error: not callable value
}
(This one actually to be counted as a bug - I created a dedicated ticket).
This all brings us to the next section:
"Consider using mixin-based maps".
Notice that while "variable-based maps" (aka DRs) seem to be a more wide-spread pattern by now, there are five different methods to define a map in Less (and infinite number of these methods permutations to define an N-dimensional (aka "nested") map).
Each method has its pros and cons, and so far it's not clear which one to be chosen as the "go-to" one (in a long run there's tendency to unify them as tidy as possible but so far it's far from that).
Now look at the structure you're trying to represent w/o sticking to "variable -> #variable" stereotype. Does not it look like a regular CSS ruleset:
.typography {
.h1 {
font: roboto;
font-weight: 300;
font-size: 9.6rem;
line-height: 9.6rem;
text-transform: none;
}
}
?
And this way you've already got a "mixin-based map" you can use pretty much the same way you'd use a "variable-based map". (Actually the current documentation for "Maps" also suggest both methods w/o enforcing either one as "the primary").
The only modification you'll need for this "CSS" structure is to make its inner or outer (or both) rulesets to be a parameteric mixin (by adding ()) so that the rules won't appear in the compiled CSS by default.
E.g. like this:
.typography {
.h1() {
...
Or like this:
.typography() {
.h1 {
...
(Also if you prefer for these identifiers you can use # instead of .).
Now getting back to your use-case (The Solution):
.typography {
.h1() {
font: roboto;
font-weight: 300;
font-size: 9.6rem;
line-height: 9.6rem;
text-transform: none;
}
}
#usage-1 {
// "expand" the set of rules:
.typography.h1(); // OK
}
#usage-2 {
// use individual value from the map:
r: .typography.h1[font]; // OK
}
#usage-3 {
// iterate through:
each(.typography.h1(), <...>); // OK
}
// etc.
Not a surprise counting that expanding a set of rules is what the mixins were invented for in the first place.
The only fundamental difference (beside current limitations/issues on how they can be used) between "variable-based" and "mixin-based" maps to keep in mind is that "variables (and properties) override" and "rulesets (and thus mixins) cascade". This may affect some particular details when you'll need your CSS data to be customized/modified by "external code" (e.g. as in "theming/subtheming" etc.) - but that's another big story so I won't get into it here, although see the next section for some tips.
"Mixins and variables interop".
And one more important thing to understand about mixins (in context of the use-case).
If we'll think of variables as an abstract programming thing, i.e. "an identifier (symbolic name) associated with a value" we quickly see that a mixin is just that: a variable.
A "mixin" (its name) is really nothing but an identifier to refer to a value, i.e. -> variable.
It's just the identifier characters (# or . in front) plus a limitation on what kind of values it can hold is what makes it to be referred to by a different title, i.e. "mixin" instead of a "variable" (as in "Less #variable").
In other words, when it comes to "I have some data and I need something (i.e. "a variable") to hold/represent it", it's important to not automatically fall into the "a variable (in a generic sense) -> #variable" trap.
So getting back to the Q, another trick to have in mind is to know that mixin and variable values (specifically if it's a "ruleset" value) can be (almost) freely assigned/reassigned to each other. I.e. basically, you can create a variable to refer to a mixin-based map and create a mixin to refer to a variable-based map.
This may be valuable to overcome current issues/limitations (mostly in usage) of both methods (or if you just prefer more of #, . or # "code-look" where maps are used).
Here're a few tips:
// ................
// "Universal" map:
.typography {
.h1() {
font: roboto;
font-weight: 300;
font-size: 9.6rem;
line-height: 9.6rem;
text-transform: none;
}
#h1: {.typography.h1}; // assign mixin to variable
.h2() {#h1()} // assign variable to mixin
.h3() {.typography.h1} // assign mixin to mixin
#h2: #h1; // assign variable to variable
}
#typography: {.typography}; // assign mixin to variable
.graphytypo {.typography} // assign mixin to mixin
// etc.
// ................
// Usage:
#usage-1 {
// use individual values from the map (all roboto):
1: .typography.h1[font];
2: .typography[#h1][font];
3: .typography.h2[font];
4: .typography.h3[font];
5: .typography[#h2][font];
6: #typography[#h1][font]; // <- like your original map
7: .graphytypo.h3[font];
// etc.
}
#usage-2 {
// expand a set of .h1 rules (all the same):
.typography.h1();
.typography.h2();
.graphytypo.h3();
// etc.
}

Related

Is it possible to un-nest (or re-nest) a selector block in LESS?

Say I have some LESS styling like:
.some-context {
.some-parent {
.some-nav {
a {
color: blue;
&.active { color: black; text-decoration: underline; }
}
}
}
}
Basically, we were styling links within one particular deep context in a certain way.
But now we have a second context that needs the same link styling.
I know I can use & to repeat the parent selector, but is there a way to "unset" the parent selector? Instead of re-using/re-arraging the parent selector, I want to discard it.
(I have used &:extend() to "steal" styling of other parts of the page from another context, but ends up quite fragile — quietly breaking whenever the other code/nesting ever changes. So I'm looking for alternatives.)
Is there a way to do something like:
// (deep within a nested context)
a, ⅋ .other-context a {
// …
}
…where whatever actual syntax "⅋" is standing in for would mean "reset the context and discard all parent selectors"?
Unfortunately, it is not currently possible (as of December 2018), but there is an open github feature request that can be found here. However, if you consider the option of switching to SASS, then you could use its #at-root directive.

How to use the default variable for only one of a few variables in less

Here is my mixin
.test(#color:black; #width:100px; #height:50px) {
width:#width;
height:#height;
background:#color;
}
Here is where it's called later
.mydiv {.test('use-mixin-color'; 300px; 150px);}
How can I override the size of .mydiv, while using the color defined in the mixin?
Everything I have tried overrides the mixin color.
To Use mixin in LESS, pass those parameter to override mixin default value :
Soluations :
.test(#color:black; #width:100px; #height:50px) {
width : #width;
height : #height;
background : #color;
}
.mydiv {
.test(#width : 300px; #height : 150px);
}
OUTPUT :
.mydiv {
width: 300px;
height: 150px;
background: black;
}
Helpful :)
In addition to the accepted answer. There're multiple methods (actually infinite) but if you want your mixin to be most easy for use you can provide a "specialization" for a specific argument value or number of arguments. Like this for example:
// usage:
.foo {.test(red, 1px, 2px)}
.bar {.test(3px, 4px)}
// impl.:
.test(#color, #width, #height) {
width: #width;
height: #height;
background: #color;
}
.test(#width, #height) { // <- "no color" specialization
.test(black, #width, #height);
}
Demo.
Also think twice before adding default parameter values for a mixin like:
.test(#color: black, #width: 100px, #height: 50px) { ...
People tend to overuse this feature while it's rarely really necessary (and only creates an extra code-noise) except some specific use-cases.
I.e. consider if you actually expect your mixin to be invoked as:
test;
test(blue, 4em);
// etc.
Do you?
It's usually a good idea to start without default parameter values (at least to protect the mixin against accidental misuse), i.e.:
.test(#color, #width, #height) { ...
and add them later only where and when they are necessary.

Can whole declaration be stored as #variable value in Less?

All my Less variables are editable within a CMS-module and are assigned to the Less compiler. It works, if I only use the values like color, font-size, etc.:
body {
background-color: #bgColor;
}
I've created another field for custom Less, which I would like to add at the end of my Less file, like:
body {
background-color: #bgColor;
}
#customLess /* desired OUTPUT: body { color: white; }*/
Unfortunately this leads to an ParseError.
I'd like to avoid to merge the existing Less and custom Less. I'm not looking for mixins, I guess.
Is it possible to put whole declarations in a #variable?
It is very much possible to put whole declarations (including the selector, property + value pair) inside a variable. Those are called as detached rulesets.
While calling them, braces (()) must be added. If not, the call will fail and result in compilation error. Below is an extract from the official website.
Parentheses after a detached ruleset call are mandatory. The call #detached-ruleset; would NOT work.
#customLess: {
body{
color: white;
}
};
#bgColor: red;
body {
background-color: #bgColor;
}
#customLess();

Overriding mixins in LESS

when defining a mixin multiple times in LESS, and later calling that mixin as follows
.background-color() {
background: red;
}
.background-color() {
background: yellow;
}
body {
.background-color;
}
the result will be a combined output from all the defined mixins
body {
background: red; // << output from mixin #1
background: yellow; // << output from mixin #2
}
while when you apply the same scenario in both Sass & Stylus ( using their own syntax of course ), when you call a mixin that is defined multiple times across your stylesheets, Only the last defined one will be executed ( it will override all previously defined mixins ) as follows.
result Sass and Stylus
body {
background: yellow; // << output from mixin #2
}
how can I override a mixin in LESS so that the output will be from the last defined mixin ?
You can not override them, alternatively use a variable to define the 'background-color'. For Less variables the last declared win.
Also read Pattern-matching
In Less all matching mixins are compiled in the source. You can use namespace to prevent name collisions, for instance:
#ns1 {
.background-color() {
background: red;
}
}
#ns2 {
.background-color() {
background: yellow;
}
}
than you can use:
body {
#ns2 > .background-color;
}
Double properties are also not removed to make some browser hacks possible, example:
#myElement {
width: 300px;
width: 500px\9;
}
To find a solution for your use case you should reformulate your question and explain why you have these same named mixins in the first place.

LESS CSS: selector substitution?

This is an existing general css rule (original file):
.caption-top {
color: red;
}
This is schematic, because in real life case, I need the .caption-top selector to become something else, depending on the context. But I would like to use a variable instead of changing the all occurrences of the selector. For example, in one context, it should become .field-name-field-caption-top. So I did this (wrapper file):
#caption-top: .field-name-field-caption-top;
#caption-top {
color: red;
}
This generates a LESS parse error. Is there another method to establish a rule to substitute a selector? So that, for the above example, the rule will finally look like this:
.field-name-field-caption-top {
color: red;
}
Additional info
The whole point is to not touch the original css file, because it comes from outside and will be overwritten, but instead, to wrap it and tell Less how to replace existing classes with classes used in a particular theme. If it is not possible to achieve, then acceptable solution will be to change the original file in an automatic way, like e.g. replace all occurrences of ".caption" with "#caption" (which I suggested in the above code sample) or make an import at the beginning etc. Then use a wrapper Less file (aware of the theme implementation) to specify what classes whould be replaced with what.
You can use escaping to achieve this:
#selector: '.myclass';
(~'#{selector}') {
color: red;
}
However you cannot do this:
(~'#{selector}') .another {
color: red;
}
To achieve the above you will need to alter the variable
#selector: '.myclass .another';
You need to produce a function of two arguments that generates the desired CSS:
.generator(#fieldName, #fieldCaption) {
.#{fieldName}-#{fieldCaption}-top {
color: red;
}
}
.generator(foo, bar);
(Feel free to try this in the online less compiler)
This piece of code produces the desired CSS for elements with name "foo" and caption "bar". You just need to make more calls to the .generator function with different arguments to obtain what you need.
If this does not correspond to what you need, please provide one additional example of your desired CSS output.
It looks like mixins are what you need:
.caption-top {
color: red;
}
.field-name-field-caption-top {
.caption-top
}
You can define a class that, used or not, you can then reference again and again inside other selectors. You can even combine them with new styles, thereby extending what the original block of CSS would have done:
.field-name-field-caption-bottom {
font-size: 3em;
.caption-top
}
Give it a go in the compiler!