LESS CSS: Reuse generated .#{name} class as a mixin - css

I'm using LESS CSS 1.3.3. Sorry if this question has already been asked, I didn't find anything relevant on the web.
I have several class generators that look like this (example extremely simplified, just enough to trigger the error):
#genMarginTop (#name, #size) {
.#{name} { margin-top: #size; }
}
Then I use them to generate some actual classes:
#genMarginTop(mtStandard, 40px);
#genMarginTop(mtHalf, 20px);
So far, so good, LESS correctly generates those classes and I can use them in the HTML.
However when I want to reuse such a generated class as a mixin somewhere else, I get an error:
.someClass {
.mtStandard; // won't work, see error below
// more stuff
}
The error I get is:
NameError: .mtStandard is undefined in /.../example.less:161:4
160 .someClass {
161 .mtStandard;
162 // more stuff
Of course I try to use the mixin after the class has been generated. It looks like LESS somehow won't register such generated classes internally after it generates them, but I could well be wrong.
Is there a way to reuse such generated classes as mixins in other classes? Being quite new with LESS, and their documentation being rather sparse about generated classes, I'm at a total loss (especially since this is the only syntax that seems to be accepted for mixins).
Thanks for reading me.
Note: The reason why I use such class generators is because they are much more complex than the example above (think nested classes that all depend on a common set of parameters), and I'm embedding the generated classes in various #media queries to support any device type in a "Zen" fashion. In the end I get something like:
#media (max-width: 1024px) {
#genSomething(something, somethingParam1, ...);
#genSomething(somethingElse, somethingElseParam1, ...);
#genStuff(stuff, stuffParam1, ...);
}
#media (max-width: 240px) {
#genSomething(something, somethingParam2, ...);
#genSomething(somethingElse, somethingElseParam2, ...);
#genStuff(stuff, stuffParam2, ...);
}
// etc
Solution / test case
Here's a test case for #MartinTurjak 's solution, I can confirm that this works as expected, nested classes and everything:
.explicit {
margin-top: 1;
input { margin-top: 1; }
}
.reuseExplicit {
.explicit;
margin-bottom: 1;
}
#generator (#arg) {
margin-top: #arg;
input {
margin-top: #arg;
}
}
.generated { #generator(1); }
.reuseGenerated {
.generated;
margin-bottom: 1;
}
Which correctly generates: (notice how explicit/generated yield the very same result)
.explicit {
margin-top: 1;
}
.explicit input {
margin-top: 1;
}
.reuseExplicit {
margin-top: 1;
margin-bottom: 1;
}
.reuseExplicit input {
margin-top: 1;
}
.generated {
margin-top: 1;
}
.generated input {
margin-top: 1;
}
.reuseGenerated {
margin-top: 1;
margin-bottom: 1;
}
.reuseGenerated input {
margin-top: 1;
}

Unfortunately. The selector interpolation is just string interpolation, and the string gets then printed into css, so no class object is generated in the less run.
So you can design a generator/mixin, that includes your operation:
#genMarginTop (#size) {
margin-top: #size;
}
But then build classes by calling the mixins / generators:
.mtStandard {#genMarginTop(40px);}
.mtHalf {#genMarginTop(20px);}
And this way they are class objects that you can use for mixin =)
.someClass {
background-color: #FFF;
.mtStandard;
//more of this stuff
}
This looks a bit silly in this simple example, but maybe something like this:
#bggenerator (#color) {
background-color: #color;
}
#bggenerator (#color, dark) {
#blend : #color + #842210;
background-color: darken(#blend, 30%);
}
#bggenerator (#color, #url, #rest) {
background: "#{color} url('#{url}') #{rest}";
}
.mtStandard {
#genMarginTop(40px);
}
.someClass {
.mtStandard;
#bggenerator(#FFF, "bgimage.png", left top no-repeat);
//more of this stuff
}
Or something that does even more exciting stuff with the arguments

UPDATE LESS 1.7+ (Works as Desired)
The .#{name} syntax will now work just as the original question had desired.
LESS 1.4+ Workaround to Actually Use Dynamic Class Names
I came up with a work around for this while working on another question, so I'm posting it as a second answer, since it goes in a totally different direction than my earlier answer.
This solution requires a couple of steps (so is not as convenient as a final fix in LESS would be), but would give actual functionality of being able to use dynamically generated class names.
First: Define your dynamic classes
This is just as you planned.
#genMarginTop (#name, #size) {
.#{name} { margin-top: #size; }
}
#genMarginTop(mtStandard, 40px);
#genMarginTop(mtHalf, 20px);
Second: Compile that file into CSS
So lets say you compile your dynamicClasses.less into dynamicClasses.css. This causes the dynamic class names to "resolve" to actual classes.
Third: Import that CSS as LESS into a 2nd LESS file that uses the dynamic class names
Using type casting for #import, we do this:
#import (less) dynamicClasses.css;
This takes those resolved class names in the dynamicClasses.css file and imports them as LESS, which makes all the class names now available as mixins. So you can do as you desired:
.someClass {
.mtStandard; // will work
// more stuff
}

I agree. It looks like LESS does not register those classes for mixin purposes.
Incomplete Solution
This LESS code:
#genMarginTop (#name, #size) {
#genMarginTopNameCheck: #name;
.get(#name) when (#name = #genMarginTopNameCheck) { margin-top: #size; }
.#{name} { .get(#name); }
}
#genMarginBot (#name, #size) {
#genMarginBotNameCheck: #name;
.get(#name) when (#name = #genMarginBotNameCheck) { margin-bottom: #size; }
.#{name} { .get(#name); }
}
#genMarginTop(mtStandard, 40px);
#genMarginBot(mbStandard, 20px);
#genMarginTop(mtSpecial, 80px);
.myClass {
.get(mtStandard);
.get(mbStandard);
}
.myClass2 {
.get(mtSpecial);
.get(mbStandard);
}
Generates this CSS
.mtStandard {
margin-top: 40px;
}
.mbStandard {
margin-bottom: 20px;
}
.mtSpecial {
margin-top: 80px;
}
.myClass {
/* NOTE the mtStandard definition is missing here !!! */
margin-bottom: 20px;
}
.myClass2 {
margin-top: 80px;
margin-bottom: 20px;
}
Explanation and Disscussion of Final Issue to Resolve
Each mixin is defining a guarded .get() mixin based off the #name to get the styles, and that is cross checked to a unique NameCheck variable name for that mixin. All your actual code is defined in the .get(), and that mixin is used to actually generate the .#{name} class code.
This works fine every time for generating the actual class name. However, the getter function at present is only working for the class name last defined by a use of the mixin. So as you can see above, my get call for mtStandard is not working because my setting of mtSpecial has apparently overwritten the #genMarginTop .get() mixin with the mtSpecial definition.
Now I assume you are going to want to call #getMarginTop and your other such mixins more than once, so obviously this is still an incomplete solution. I've figured out how you can get the class generated by the top level mixin to be used as a 'mixin' for another class using the .get(), but I haven't figure out how to make the .get() not get overridden when the top level mixin is called again.

Related

Name clash between Less mixin and CSS selector

I have this simplified Less script
.placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
.placeholder();
}
.placeholder {
margin-top: 20px;
}
The output when I run this through my local compiler or winless online less compiler is
input {
margin-top: 20px;
}
input::-webkit-input-placeholder {
color: #333333;
}
.placeholder {
margin-top: 20px;
}
Insted of the desired output
input::-webkit-input-placeholder {
color: #333333;
}
.placeholder {
margin-top: 20px;
}
Is this a bug or am I missing something here?
By the result it looks to me like I can't have CSS-selectors with the same name as mixins with default values.
I'm running into this problem when compiling Bootstrap with my site specific code. In this particular case I can work around it, but as the project grows and I include other projects I can't imaging I have to keep track of any mixins with default values?
Edit: I see now that I should have read the manual and pretty much seen on the first page of the docs that everything can be treated as a mixin.
In Less, everything is technically a mixin irrespective of whether we write it with parantheses (as in with parameters) or without parantheses (as in like a CSS class selector). The only difference between the two is that when the parantheses are present, the properties present within it are not output unless called from within a selector block.
Quoting the Less Website:
It is legal to define multiple mixins with the same name and number of parameters. Less will use properties of all that can apply.
In this case, since the other mixin has a default value for its only parameter, both the properties can apply when called without any parameter and hence there is no way to avoid it from happening.
Workaround Solution: One possible solution to work-around this problem is to enclose all such conflicting rules within a parent selector (like body).
.placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
.placeholder();
}
body{
.placeholder{
margin-top: 20px;
}
}
Compiled CSS:
input::-webkit-input-placeholder {
color: #333333;
}
body .placeholder {
margin-top: 20px;
}
Option 2: Extracted from the solution posted by seven-phases-max in the Less GitHub Issue thread.
For the particular use-case one of possible workarounds is to isolate conflicting classes in unnamed scope so they won't interfere with external names:
.placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
.placeholder();
}
& { // unnamed namespace
.placeholder {
background: #ffffff;
}
} // ~ end of unnamed namespace
Note: The above is a straight copy/paste from the GitHub thread without any modifications so as to not tamper with the information.
#mixin placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
#include placeholder();
}
.placeholder {
margin-top: 20px;
}
that should work.
So if i understood right, you just want to add 20px on top of the placeholder ? Add padding-top to input instead.
input {
padding-top: 20px;
}

Dynamic :last-child in SASS

I've been messing around with SASS for a while and have made some nice functions that makes use of #if #else if etc. However I have the following two pieces of code on a Mixin:
&:last-child { float: right; margin-right: 0; }
&:only-child { float: left; margin-right: 0; }
This obviously applies both of these pieces of code to every element I include this particular Mixin on. Is there a way of dynamically checking whether an element is a :last-child / :only:child with an #if statement or any other methods?
Something like:
#if :last-child == True { float: right; margin-right:0; }
Hopefully I've explained that well enough.
Thanks
Sass doesn't know anything about your document. It can't check if the element is the first-child or only-child. Sass can only be used to generate valid CSS.
If you're looking for the last-child that is not an only-child, this is your selector:
&:last-child:not(:only-child) {
// styles
}
http://tinker.io/1717b
If you're wanting to disable output of one or the other (because you already know that only one of them will be applicable in this instance), then you'll need an extra argument passed to your mixin:
#mixin foo($children: true) {
#if $children or $children == last-child {
&:last-child {
// styles
}
}
#if $children or $children == only-child {
&:only-child {
// styles
}
}
}

How to dynamically generate an entire class and concatenate it to its parent selector using LESS

So far I have this (LESS language):
LESS:
.test(#name) {
(~".sm-#{name}") {
background: ~"url(../images/icons/sm/#{name}-16x16.png)";
}
}
and I can call it (.test("facebook")) to generate something like this:
CSS:
.sm-facebook {
background: url(../images/icons/sm/facebook-16x16.png);
}
However, I'm not able to concatenate this generated class definition to a parent selector using the usual & operator. I would expect
LESS:
ul {
li {
&.test("facebook");
}
}
to generate
CSS:
ul li.sm-facebook {
background: url(../images/icons/sm/facebook-16x16.png);
}
but I am getting a parse error.
How can I do this?
A LESS 1.4 Answer
At the time of initial answer, I had not resolved if there is a way to do this in LESS 1.3.3 (but later did, see update below). I had found that the current LESS 1.4 (beta) can achieve it by putting the concatenation in the mixin itself this way (note the lack of quotes on the argument):
LESS
.test(#name) {
&.sm-#{name} {
background: ~"url(../images/icons/sm/#{name}-16x16.png)";
}
}
ul{
li {
.test(facebook);
}
}
CSS Output
ul li.sm-facebook {
background: url(../images/icons/sm/facebook-16x16.png);
}
If you need flexibility to have it concatenate or not, you can do a nested mixin like so (this one defaults to attach):
.test(#name, #attach: 1) {
.attach(1) {
&.sm-#{name} {
background: ~"url(../images/icons/sm/#{name}-16x16.png)";
}
}
.attach(0) {
.sm-#{name} {
background: ~"url(../images/icons/sm/#{name}-16x16.png)";
}
}
.attach(#attach);
}
Then use it like this .test(facebook); to concatenate, and this .test(facebook, 0); to separate it as a child (or standalone) class.
Update: A LESS 1.3.1 to 1.3.3 Answer
Change the name first so that the string evaluation is separate from the class name assignment. Other points made above about flexibility can be used for this answer as well.
.test(#name) {
#addName: ~"sm-#{name}";
&.#{addName} {
background: ~"url(../images/icons/sm/#{name}-16x16.png)";
}
}

Extending selectors from within media queries with Sass

I have an item class and a compact "modifier" class:
.item { ... }
.item.compact { /* styles to make .item smaller */ }
This is fine. However, I'd like to add a #media query that forces the .item class to be compact when the screen is small enough.
On first thought, this is what I tried to do:
.item { ... }
.item.compact { ... }
#media (max-width: 600px) {
.item { #extend .item.compact; }
}
But this generates the following error:
You may not #extend an outer selector from within #media. You may only
#extend selectors within the same directive.
How would I accomplish this using SASS without having to resort to copy/pasting styles?
The simple answer is: you can't because Sass can't (or won't) compose the selector for it. You can't be inside of a media query and extend something that's outside of a media query. It certainly would be nice if it would simply take a copy of it instead of trying to compose the selectors. But it doesn't so you can't.
Use a mixin
If you have a case where you're going to be reusing a block of code inside and outside of media queries and still want it to be able to extend it, then write both a mixin and an extend class:
#mixin foo {
// do stuff
}
%foo {
#include foo;
}
// usage
.foo {
#extend %foo;
}
#media (min-width: 30em) {
.bar {
#include foo;
}
}
Extend the selector within a media query from the outside
This won't really help your use case, but it is another option:
%foo {
#media (min-width: 20em) {
color: red;
}
}
#media (min-width: 30em) {
%bar {
background: yellow;
}
}
// usage
.foo {
#extend %foo;
}
.bar {
#extend %bar;
}
Wait until Sass lifts this restriction (or patch it yourself)
There are a number of ongoing discussions regarding this issue (please don't contribute to these threads unless you have something meaningful to add: the maintainers are already aware that users desire this functionality, it's just a question of how to implement it and what the syntax should be).
https://github.com/sass/sass/issues/1050
https://github.com/sass/sass/issues/456
For the record, here is how I ended up solving the problem with only duplicating generated styles once:
// This is where the actual compact styles live
#mixin compact-mixin { /* ... */ }
// Include the compact mixin for items that are always compact
.item.compact { #include compact-mixin; }
// Here's the tricky part, due to how SASS handles extending
.item { ... }
// The following needs to be declared AFTER .item, else it'll
// be overridden by .item's NORMAL styles.
#media (max-width: 600px) {
%compact { #include compact-mixin; }
// Afterwards we can extend and
// customize different item compact styles
.item {
#extend %compact;
/* Other styles that override %compact */
}
// As shown below, we can extend the compact styles as many
// times as we want without needing to re-extend
// the compact mixin, thus avoiding generating duplicate css
.item-alt {
#extend %compact;
}
}
I believe SASS/SCSS does not support the #extend directive inside of a media query. http://designshack.net/articles/css/sass-and-media-queries-what-you-can-and-cant-do/
You might need to use a mixin instead, though the code bloat needs to be weighed against your objective.
This is the cleanest, partial solution I've found. It takes advantage of #extend where possible and falls back to mixins when inside media queries.
Cross-Media Query #extend Directives in Sass
See the article for full details but the gist is that you call a mixin 'placeholder' that then decides whether to output #extend or an #include.
#include placeholder('clear') {
clear: both;
overflow: hidden;
}
.a {
#include _(clear);
}
.b {
#include _(clear);
}
.c {
#include breakpoint(medium) {
#include _(clear);
}
}
Ultimately it may not be better than just using mixins, which is currently the accepted answer.
I use breakpoints, but it's the same idea:
#mixin bp-small {
#media only screen and (max-width: 30em) {
#content;
}
How to use it:
.sidebar {
width: 60%;
float: left;
#include bp-small {
width: 100%;
float: none;
}
}
There is a text about mixins where you can find out more about this option.
Could you restructure?
.compact { //compact-styles }
.item {}
.item.compact { #extend .compact }
#media (max-width: 600px) {
.item { #extend .compact; }
}
If I understand the documentation correctly, that should work. I think the reason the way you're trying won't work is that it doesn't see .item.compact when it's parsing the #extend, but that's an uninformed guess, so take that with a truck load of salt! :)

Less - How to insert an #variable into property (as opposed to the value)

In less.js, I'm able to replace values with variables with no problems.
#gutter: 20px;
margin-left:e(%("-%d"), #gutter);
When trying to replace properties with variables, I get errors. How would I perform the following in Less?
#gutter: 20px;
#direction: left;
e(%("margin-%d"), #direction):e(%("-%d"), #gutter);
Thanks to Alvivi for the solution and research (you get the reward for that). I decided to add the following as the actual answer since this is a real way to set it up instead of looking at .blah() pseudo code..
Here's a real strategy for setting it up:
#gutter: 20px;
#dir: left;
#dirOp: right;
then create mixins to enhance margin and padding like so:
.margin(left, #dist:#gutter) {
margin-left:#dist;
}
.margin(right, #dist:#gutter) {
margin-right:#dist;
}
.padding(left, #dist:#gutter) {
padding-left:#dist;
}
.padding(right, #dist:#gutter) {
padding-right:#dist;
}
.lr(left, #dist: 0) {
left: #dist;
}
.lr(right, #dist: 0) {
right: #dist;
}
.. then you can just
#selector {
.margin(#dir);
}
or
#selector {
.margin(#dirOp, 10px);
}
all together:
#selector {
.margin(#dir);
.margin(#dirOp, 50px);
.padding(#dir, 10px);
.padding(#dirOp);
float:#dir;
text-align:#dirOp;
position:absolute;
.lr(#dir);
}
Easy breezy LTR/RTL with LESS! Woot!
Escaping, as says the documentation, is used to create CSS values (not properties).
There is a discussion with some workarounds here. One would be using parametric mixins. For example:
.g () { /* Common properties */ }
.g (right) { margin-right: e(...) }
.g (left) { margin-left: e(...) }

Resources