Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixins should accept LESS blocks #965

Closed
rbu opened this issue Oct 1, 2012 · 65 comments
Closed

Mixins should accept LESS blocks #965

rbu opened this issue Oct 1, 2012 · 65 comments

Comments

@rbu
Copy link

rbu commented Oct 1, 2012

It would be helpful if mixins had access to a content block passed to them, so one could encapsulate media queries or browser hacks in a central place and reference them symbolically.

This is basically the "Passing Content Blocks to a Mixin" feature of SASS:
http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#mixin-content

In LESS this would mean something like:

.mobile() {
  @media all and (max-device-width: 480px) {
    @content
  }
}
.big-desktop-button {
  ...
  .mobile {
    display:none;
  }
}
@dwt
Copy link

dwt commented Oct 2, 2012

Oh, I need this too!

@caseyohara
Copy link

+1

@BruceClark
Copy link

Absolutely. Please!

@caseyohara
Copy link

I can see some collisions by adding this functionality to the normal mixin syntax. Just by looking, it would impossible to tell if the call above would compile to

.big-desktop-button .mobile {
  display: none;  
}

or

@media all and (max-device-width: 480px) {
  .big-desktop-button {
    display: none;    
  }
}

So I think it would need new syntax. Here is my idea.

Allow blocks of LESS to be passed into parametric mixins, as a normal positional parameter surrounded by braces.

.mobile(@content) {
  @media all and (max-device-width: 480px) {
    @content
  }
}

.big-desktop-button {
  ...
  .mobile({
    display:none;
  });
}

Due to variable scoping and evaluation, I imagine this would require that mixins that accept LESS content blocks can only accept content blocks.

@PiotrSkon
Copy link

+1 This is a must.

@Soviut
Copy link

Soviut commented Nov 28, 2012

+1 SASS can do this.

@cescalante
Copy link

+1

@matthew-dean
Copy link
Member

I don't understand these examples. What is @content defined as in the first example?

@chimericdream
Copy link

With SASS, you can pass a whole content block to a mixin independent of the parameters. So you have something like this:

.mobile() {
    @media your-query-here {
        @content
    }
}

.someclass {
    // some general styles here
    .mobile() {
        // your mobile styles here
    }
}

that would compile to:

.someclass {
    // some general styles here
}

@media your-query-here {
    .someclass {
        // your mobile styles here
    }
}

I'm adding my support for this. Real logic operators (if/else) and this feature are the biggest things keeping me from using LESS at this point.

@davidrivers
Copy link

+1!!!

@jonschlinkert
Copy link
Contributor

I'm still confused by this. Some real world examples of this being used would really help a lot. I tried to read through the SASS docs, but I still struggled with it. It sounds like @content is really just passing a literal block of CSS to the bottom of the inheriting declaration block. Is that right?

@davidrivers
Copy link

@jonschlinkert You're right. It's extremely useful when you need a "wrapper" selector. For example, I have a project where we have to share the page with styles from a third-party, so we "pseudo-namespace" our styles in order to make them more specific than the third party styles. We do this by wrapping all of our styles with an arbitrary selector, and we can nest styles within that selector and leverage the fact that a complex selector such as .wrapper .module { /* styles here*/ } is more specific than most or all of our third-party's selectors (and therefor shouldn't be affected by them since we also embed our own "reset" styles on that wrapper before specifying styles that we want).

Another use for having an arbitrary selector to wrap styles is if you want to reuse Sass or Less "modules" between different projects or documents and for some reason you need to give them different wrapper hooks in your markup (or want the flexibility to do so).

My use cases are attempting to bridge the fact that the CSS cascade lacks a concept of a true namespace, but you can leverage a stylesheet preprocessor to get something practically close to one. "Media query bubbling" is a similar use case (since it allows "blocks" of styles to be passed around and wrapped with different selectors), although I don't know how Less supports this internally, but a cursory Google search suggests that Less does support this feature.

@jonschlinkert
Copy link
Contributor

@davidrivers thanks, the wrapper analogy helps, that's sort of how I saw it in my mind. Looking at this semantically, this is a better use case for the term @include than @content, which makes no sense to me here. (Sass had to pick a different term since they used @include for mixins.)

David if you find some time, would you mind adding some detailed code examples here so when @MatthewDL and @lukeapage review again they can make a better evaluation of this? thanks!

@matthew-dean
Copy link
Member

Yes, LESS supports media query bubbling. There's probably ways to do namespacing as you describe, but like @jonschlinkert, the examples are still a head-scratcher. That is, I can't see how it's not currently supported. You can segment code into, say, mobile-specific definitions using variables / mixin guards.

@voodoom
Copy link

voodoom commented Mar 5, 2013

+1!!

@lukeapage
Copy link
Member

I wonder if extend and extend mixins (equivalent to sass placeholders) actually provide this functionality..

anyway, here is the example from the sass docs, converted into pseudo less.

.apply-to-ie6-only() {
  * html {
    @content;
  }
}
.apply-to-ie6-only() {
  #logo {
    background-image: url(/logo.gif);
  }
}

output

* html #logo { background-image: url(/logo.gif); }

usecase - stop repetition of selectors/media queries throughout the code and abstract them out to one place.

here is how I might do it with extend

.apply-to-ie6-only {
  #logo {
    background-image: url(/logo.gif);
  }
}

* html:extend(.apply-to-ie6-only all) {
}

disadvantages

  • it doesn't remove the apply-to-ie6-only, it augments it.
  • it doesn't allow abstraction of media queries

here is how I might do it with mixins

.apply-ie6-only() {
  #logo {
    background-image: url(/logo.gif);
  }
}

*+html {
   .apply-ie6-only();
}

note this does work with multiple apply-to-ie6-only classes and because of media bubbling it does work applying media queries to multiple classes.

disadvantages

  • cannot apply a custom selector everywhere, only at the beginning of the selector

I can't think of a nice way of implementing a @content feature in less.. and I am also not sure what it brings to the table above my two work-arounds. What do you think @davidrivers ?

Another proposed feature that might be useful for your specific case is "silent" imports. You could wrap everything in a class, import your library silently, then extend a particular namespace.. then the new namespaces selectors would be brought in and the old selectors kept quiet...

@dwt
Copy link

dwt commented Mar 10, 2013

I'm not sure I completely understand your examples, but they seem to all be backwards in a way - sorry if I'm getting them wrong.

What I (and I the thread opener who is a collegue of mine) want to achieve is that I can group the media query specific styles with the other styles to a particular page element.

The idea here is that this solves the problem that using media queries always means you have to look in many files / places in the same file to find all the styles that pertain to one specific element.

The solution to this now should allow that you can write styles more along the lines of this:

.generic-class {
  .more-specific-class {
    // style declarations
    .something-that-makes-the-contained-styles-only-apply-on-mobile-devices {
      // mobile specific style declarations
    }
    .something-that-makes-the-contained-styles-only-apply-on-tablet-devices {
      // tablet specific style declarations
    }
    .something-that-makes-the-contained-styles-only-apply-on-desktop-devices {
      // desktop specific style declarations
    }
  }
}

That way the wrapping structure of less can be used to really group all styles for an element together and see the different responsive elements in one piece. No matter the solution - this is what it should allow to achieve to solve this bug.

@SomMeri
Copy link
Member

SomMeri commented Mar 10, 2013

@dwt I think that media bubbling is the feature you are looking for.

Documentation:

To make it work in your example, you have to replace:

  • .something-that-makes-the-contained-styles-only-apply-on-mobile-devices by @media mobile
  • .something-that-makes-the-contained-styles-only-apply-on-tablet-devices by @media tablet
  • .something-that-makes-the-contained-styles-only-apply-on-desktop-devices by @media desktop.

Modified version of your example:

 .generic-class {
   .more-specific-class {
      content: "style declarations";
      @media mobile {
        content: "mobile specific style declarations";
      }
      @media tablet {
        content: "tablet specific style declarations";
      }
      @media desktop {
        content: "desktop specific style declarations";
      }
    }
 }

Less.js-1.3.3 compiled it into this:

 .generic-class .more-specific-class {
   content: "style declarations";
 }
 @media mobile {
   .generic-class .more-specific-class {
     content: "mobile specific style declarations";
   }
 }
 @media tablet {
   .generic-class .more-specific-class {
     content: "tablet specific style declarations";
   }
 }
 @media desktop {
   .generic-class .more-specific-class {
     content: "desktop specific style declarations";
   }
 }

Basically, @media nested inside ruleset acts as if or make exception for.

@dwt
Copy link

dwt commented Mar 12, 2013

Maybe I'm misunderstanding you, but the whole point of this bug report is being able to abstract about the media rules and I think that the media query bubling is only providing the foundation of this feature.

I.e. we want to define the actual breakpoints of the media queries at exactly one point, and then invoke those rules at various places inside the styles so that we can change them consistently and easily at exactly one point.

Does that make sense?

@SomMeri
Copy link
Member

SomMeri commented Mar 12, 2013

@dwr I misunderstood you and what you want make sense now, sorry. You could currently use only media query interpolation, but that is somewhat limited compared to this feature.

@var: ~"tablet";
@media @var {  ...   }

@dwt
Copy link

dwt commented Mar 12, 2013

Indeed I just found out that since 1.3 this is possible and a good start towards this goal. That being said, a way to abstract about blocks of less would still be very much apreciated.

@DesignByOnyx
Copy link

Here is an example that I think may help get the idea across. When compiling a stylesheet for legacy browsers, you could simply switch @isResponsive to false.

@isResponsive: true;
@handheld-up: ~"screen and (min-width: 30em)";
@handheld-down: ~"screen and (max-width: 48em)";
@handheld-only: ~"screen and (min-width: 30em) and (max-width: 48em)";
@tablet-up: ~"screen and (min-width: 48em)";
@tablet-down: ~"screen and (max-width: 60em)";
@tablet-only: ~"screen and (min-width: 48em) and (max-width: 60em)";

// @@content is a special variable with the "contents" of the style declaration
// The double @@ sign is just something I chose to distinguish it from a normal variable
.mq-style(@breakpoint) when (@isResponsive = true) {
    @media @breakpoint {
        @@content;
    }
}
// A litte duplication is necessary due to the inability to have complex guards
.mq-style(@breakpoint) when (@isResponsive = false) and (@breakpoint = @handheld-up) {
    @@content;
}
.mq-style(@breakpoint) when (@isResponsive = false) and (@breakpoint = @tablet-up) {
    @@content;
}

.some-element {
    .mq-style(@handheld-up) {
        /* This is the content be be used in place of the @@content variable */
        color: blue;
        background: red;
    }

    .mq-style(@tablet-only) { {
        border: 1px solid red;
    }
}

@DesignByOnyx
Copy link

And here is the output from my last example:

/* @isResponsive: true; */
@media screen and (min-width: 30em) {
    .some-element {
        color: blue;
        background: red;
    }
}
@media screen and (min-width: 48em) and (max-width: 60em) {
    .some-element {
        border: 1px solid red;
    }
}
/* @isResponsive: false; */
.some-element {
    color: blue;
    background: red;
}

@leads
Copy link

leads commented Apr 8, 2013

It would be great if LESS was capable of achieving this http://jakearchibald.github.io/sass-ie/

As far as I can see the only thing holding it back is the lack of support/alternative for @content.

@mhulse
Copy link

mhulse commented Apr 16, 2013

@leads yes! +1! 👍

I was just in the process of converting sass-ie to LESS when I hit a brick wall with @content. I don't see how @lukeapage's examples would work in this case.

As an example of a feature I think we don’t need to implement, the sass @content feature allows you to abstract selectors or media queries into mixins, so they appear only in one place. In our debate on whether to include @content in less, I make the point that for most use-cases the functionality is already possible with existing less features. — Via Luke Page on Scott Logic: "Less vs Sass vs Stylus"

I think the sass-ie code is a prime example of @content's usefulness. I'm not sure if you could replicate that functionality with existing LESS features (no?).

@SomMeri
Copy link
Member

SomMeri commented May 6, 2013

I think I found a workaround for this. Since mixins see and can access their callers scope, you can place media and selectors into mixin and inject content by another mixin defined inside the caller:

/*
  Usage: the caller must have .content declared in its scope!
*/
.mobile() {
  @media all and (max-device-width: 480px) {
    .content();
  }
}

/* use the .mobile inside desktop button */
.big-desktop-button {
  .content() { //this will be injected into .mobile mixin
    display:none;
  }
  // the .mobile sees this scope and will use .content mixin
  .mobile();
}

/* use the .mobile inside mobile button */
.big-mobile-button {
  .content() { //this will be injected into .mobile mixin
    display:inline;
  }
  // the .mobile sees this scope and will use .content mixin
  .mobile();
}

compiles into:

@media all and (max-device-width: 480px) {
  .big-desktop-button {
    display: none;
  }
}
@media all and (max-device-width: 480px) {
  .big-mobile-button {
    display: inline;
  }
}

@dwt
Copy link

dwt commented May 6, 2013

I would like to add another usecase to let us abstract about @media queries, ie compatibility.

Since IE 7 does not support @media queries, one possible workaround is to ensure that all styles that are in the desktop media query are also in an ie specific file and get included there. Since manual labour sucks and css is much better we have some css classes on body that let us target ie specificly from css where required with classes like .lt-ie8 or .lt-ie7. Now, if we could abstract about media queries, we could build a mixin which would put all the desktop rules into the desktop media query and into the scope of the .lt-ie8 class.

In pseudo-code I would assume this could look a bit like this

.desktop() {
 @media (something) {
  @content()
 }

 .lt-ie-9 {
  @content
 }
}

Pretty please, can we have something like this? I'd love to get rid of the special IE.css file we maintain by hand where we copy over styles once in a while and then forget to update them.

@jcc8
Copy link

jcc8 commented Aug 5, 2013

+1 - Bounty here: https://www.catincan.com/bounty/mixins-should-accept-less-blocks-issue-965-less-less-js-github

Crowdfund issue & get merged into main branch to collect.

@parisholley
Copy link

Here is a SASS mixin library I wrote using display: table that I think shows a good idea of how powerful this feature could be (semantics aside). I'm obviously making many assumptions/compromises but there is so much more you can do with this type of functionality.

https://github.com/parisholley/tablespooncss

@seven-phases-max
Copy link
Member

Just my +1 to support SomMeri's syntax proposal...
We could also have another syntax for this kind of stuff (in addition to the "expanded rule at the point of assignment"):

.block {
    size: 2;
}

#use-something {
    .something(@variable);
    @variable: .block;
    /* ^ assign any arbitrary "name" to a variable (at this point nothing indicates
    that the variable points to a mixin - it's only a "raw" value and we can use
    it just as any other variable - for example putting it as a CSS property value) 
    */
}

.something(@value) {
    @value(); 
    // ^ now the parens show that variable's value
    // is a mixin name and we want to expand it here
}

The idea behind this syntax is a possibility to provide arguments for the expansion at any level if the variable points to a parametric mixin, i.e.:

.something(@value) {
    @a: 1;
    @b: 2;
    @value(@a, @b); 
}

@seven-phases-max
Copy link
Member

I've just noticed a "minor" problem with 18462037's @variable: .block(); syntax.
Look at it... Yes, it looks exactly like calling a .block function and assigning its return value to the @variable.
And even if this feature ("mixin returning a value with direct assignment to a variable") is somewhat anticipated (?) for the moment or even barely appear in LESS at all, it would be a good idea to keep this syntax reserved just in case (as we never know how the things will evolve). After all, @variable: .mixin(); will confuse users if it has "not a function call" semantics.

So thinking of possible alternatives:
Actually, as soon as we have a "{} block as a variable or mixin parameter" syntax, i.e.:

#use-mixin {
  .mixin(@variable);
  @variable: { /* ... */};
}
#short-expression-use-mixin {
  .mixin({ /* ... */ });
}

­­... we won't need any special syntax for a "named mixin assignment" as we'll be able to do it with:

#use-mixin {
  .mixin(@variable);
  @variable: {.block()};
}
#short-expression-use-mixin {
  .mixin({.block()});
}

And though @variable: {.block()}; or @variable: {.block}; are probably not absolutely equal to 18462037's @variable: .block(); (different points of expansion?) their results promise to be similar.

@lukeapage
Copy link
Member

Just sketching and thinking about how this could be accomplished with mixin extends.. what do you think about this?

.a :extend(.b()) {
    .c {
        color: black;
    }
}
@media x {
    .b() {
    }
}
@media y {
    .b() {
    }
}

essentially you make a template, put your mixin in the template and then extend the template...

@matthew-dean
Copy link
Member

Per discussion on other issue threads, +1 to @seven-phases-max's proposal of assigning mixins to variables.

@nelsonpecora
Copy link

I'll also +1 @seven-phases-max's proposal. The .mixin(@variable); / .mixin({ key: value; key: value; }); syntax feels very intuitive for me (in fact, I tried to use it before finding out it didn't work).

@lukeapage
Copy link
Member

Implemented and will be in 1.7 release soon, see #1859. seperate issue for passing mixins.

@katomonster
Copy link

+1

@nelsonpecora
Copy link

By the by, I can confirm this is working in 1.7.

@AndrewEastwood
Copy link

is it working right now?

@seven-phases-max
Copy link
Member

@AndrewEastwood See the docs.

@AndrewEastwood
Copy link

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests