-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Interpolation of variable, mixin and function names (a.k.a. "Dynamic" variables/mixins/functions) #2702
Comments
No. You need to declare keyed dictionaries; which you can build with lists... |
@rjgotten I don't know what you mean when you say "keyed dictionaries; which you can build with lists". Can you provide a reference to an example or an example less? |
This depends on the code you need to use such "variables" in. In general yes, the need for such variable-variable-names hints that there's something wrong with the approach (just think of it: you define something with unknown name to represent a value you would refer to later by the same unknown name... It does not make any sense really). And yes, lists/arrays and more "structured" (whatever this could mean in general) programming are much more clean and straight-forward (compared to both hypothetical variable-name-variable-definition and variable-name-variable-access (which is just yet another closely related anti-pattern of "moving every thing into a dedicated global variable")). A simplest example (just example, guessing you would use those variables for some grid generation) would be: @devices:
s 544px,
m 768px,
l 991px,
xl 1200px,
z 2400px;
.make-grid-colomns();
.make-grid-colomns(@i: length(@devices)) when (@i > 0) {
.make-grid-colomns(@i - 1);
@device: extract(@devices, @i);
@name: extract(@device, 1);
@size: extract(@device, 2);
@media (min-width: @size) {
column-@{name}-bla-bla {
blee-blu: blo-bla;
}
}
} But there're too many variations/patterns and zillion of other tips and tricks to use in particular situations (for a concrete use-case it's better to ask at SO or so). |
@seven-phases-max Thanks, I now understand what he meant by "keyed dictionaries" I didn't realize you could do that, though I guess it makes sense given that lists can be separated with commas or spaces. I still think I need the functionality I filed this ticket for. Normally, I would agree that defining "something with unknown name to represent a value you would refer to later by the same unknown name... It does not make any sense really." If this were simply my own style sheet I would agree with you. However, I am trying to write a library something like bootstrap. The ability to define variable variables means that users of my library could change the number and names of the breakpoints and then have access to variables with those names. That will be much more straightforward for a user of my library than understanding how to pull things out of lists etc. So a hypothetical user of my library might do something like:
From their perspective, there are no unknown names. They know that the names should be tiny, small, and big. They directly use a variable with a reasonable name to them. |
@devices:
tiny 340px,
small 768px,
big 1200px;
@media @screen-tiny-only ... The main problem of this code is that if you add another screen size ("very-big") to the arrays above it will have no effect until you also explicitly write (And for a more complex use-case, one can always get use of something like |
That isn't a problem because I am not expecting people to add device sizes to an existing project. Rather I am assuming they will be defined project to project near the beginning of that project. |
Then there's no need for lists (that's the main point - if you use dedicated vars you don't need any lists, if you use a list of these values you don't need vars). In other words, if you expect |
I don't think you are understanding what I am doing. Let's imagine that Alice and Bob both download my library. My Library
Alice's Project
Bob's Project
Bob is able to change the device list for his project, then use natural variables. So I do need to declare variable variables. My library needs to be dynamic and then various users of it need to use explicit variables like |
Well, no, actually I do understand it perfectly. As I mentioned above if you want a fully customizable set of media queries forget about dedicated global vars. Bob's project may look like: @import "library.less";
@devices:
tiny 340px,
small 768px,
big 1200px;
.media(tiny, {
// ...
}); (just one of possible syntaxes). What I can't actually understand in your example is that how the code in your library would know what identifiers Bob used for his devices? Does the library have any media dependent CSS at all? Or this conversion from a list to global vars is the only purpose of the library? And if it's not, could you also elaborate what else (media related) code/stuff the library has? |
I have to concur with @seven-phases-max and @rjgotten. "Defining" variable variables doesn't make sense, because it is, by nature, a referential syntax. And they're suggesting other patterns to achieve the same thing, and I could think of a few more to solve the same problem. For example, if Bob or Alice need to override the value of a global var, it would be as simple as:
If, however, Bob wanted to change the NAME of that var, it would be as simple as:
...and then he could use |
First, setting aside my use case for a moment, I think it is just a matter of consistency. If you document that Second, since there are some questions about the library I am thinking of creating I will try to provide some more detail. The plan is that it would be a mixin only library. Initially, it would support responsive grids and media queries. Over time I would hope it would grow to incorporate lots of other functionality like Bootstrap except only as mixins so users weren't forced into one way of organizing styles and constantly surprised by the millions of styles the framework applies. I'm sure some of these additional mixins would be affected by the media queries. I think it would be great to add features along the lines of Compass as well though I am not that familiar with it. I like less more than Sass and think that the lack of a really great mixin library is part of what is pushing people to Sass. When creating a library, I think end user syntax and clarity is very important to adoption. It makes sense for the library authors to jump through hoops and experience pain to create a good experience for users. I am open to alternate syntaxes, but so far haven't seen one I think is as clear as what could be done if I could declare variable variables. I readily admit I am not a Less guru, so there may be a good one I am not aware of yet. With all that said, something more concrete will probably help to clarify. In the discussion below, I will describe the API of the library without going into all the mixins it would take to create that. My library should support not just re-definable breakpoints, but a variable number of breakpoints since different sites work well with a different number of breakpoints. When changing the number of breakpoints, it makes sense to rename them.
I am using two different lists because there is one more screen size name than there are breakpoints (breakpoints divide the screen sizes into ranges). Based on the screen size names (possibly overridden by a customer), there would be mixins allowing the creation of what bootstrap calls a container. This defines in much greater detail the behaviour of the page width. An example usage might be:
This allows the user to control for each screen size whether the the page is fluid or fixed size and what the margins and padding should be. In general, there are two mixins per page size When laying out elements on the page, there are a set of mixins that work similar to bootstrap grids (except they use inline-block instead of float). Rows are created using:
Then, for each screen size
This set of mixins allows users to have different numbers of columns and different gutter width for different elements. Up to this point everything I have described can be done with less today. Given that the user has used many mixins each named after the screen sizes that they have potentially customized, when they need to do some media query for a style they are applying, it seems natural to them that they should be able to use variables with the same names. i.e.
There would be a set of such variables for each screen size This last functionality is what I would use declaring variable variables for. In this context, I think it makes a lot of sense and provides the most intuitive syntax to the user. If something I have said is unclear, I'm happy to answer questions. However, at this point I think I have pretty much stated my case for this feature. If you don't agree that is fine. I leave it up to whatever process the less community has for deciding what features make it into the language. However, to keep less relevant I would strongly encourage that features like this and other powerful features needed to create advanced mixin libraries be added. I don't expect typical users to use features like this. Just like in C# and Java how many developers don't ever create a generic class, but they are integral to libraries every developer uses. |
In Less I suggest this should be: .my-page
{
.page();
.page(xs, fluid, @page-margin: 8px);
.page(s, fluid);
.page(m, fixed);
} and so on (notice if Bob defines So no, you did not convince me. |
I was also going to suggest keyword guards on mixins as @seven-phases-max. And I would also echo that Bootstrap's way of doing things wasn't what I would call "best practices" for Less code. (I suspect much of the stuff they had to simplify in order to have cross-platform support with Sass.) And, having tinkered with building a Less library (which maybe someday I'll finally finish), I can tell you that all of your user goals are possible with Less's existing feature set, just not perhaps in the exact method you're aiming at. And, with Less's (unfortunately undocumented) inline, scoped plugins, Framework authors have unparalleled power to build features that don't cause conflicts with other frameworks nor end-user styles, and don't require any extra steps on the part of the user to "install" or "build", unlike extensions for Sass and PostCSS. (See: #2479) So there's a lot of power possible there. |
Yes, sure this does not mean that we don't need other features that could make "structured" code better (in particular #1848, #2433 and so on). But this "interpolate definitions" stuff you're looking for is a wrong move because it's nothing but an attempt to workaround the fundamental limitation of predefined set of entities (vars and mixins) by introducing yet another level of indirection thus essentially just stacking kludges on top of each other, while the problem should be solved directly via proper language entities with natural arbitrary data support (structures, arrays, mixins args and so on...) to stress the weirdness: |
To not sound unfounded here's concrete example. Bootstrap .make-sm-column-offset(@columns) {
@media (min-width: @screen-sm-min) {
margin-left: percentage((@columns / @grid-columns));
}
} and same code repeated for every device in their hardcoded set. In a modern library this can be implemented as (here I'm using lists and less-plugin-lists since currently it's the easiest and the shortest way (for me at least), but there're other methods to do the same): @devices:
sm 768px,
md 992px,
lg 1200px;
// not showing media-free branch for xs (or whatever first device it may be) to keep code evident
.make-column-offset(@device, @columns) {
@media (min-width: at(@devices, @device)) {
margin-left: (@columns / @grid-columns * 100%);
}
} See? It's times less code (actually it will be even less verbose since you'll have shared @import "library.less"
@devices:
tiny 340px,
small 768px,
big 1200px;
.bobs-news-column {
.make-column-offset(small, 2.42);
} Additionally see this example. |
Yup. dictionary/map style lookup on a nested list and a generic mixin that takes the map's key as a parameter. That's what I'd suggest as well. Given unlocking mixins you could also do nifty stuff like having one mixin set the 'breakpoint key' and unlocking a set of mixins for further manipulation that curry this parameter so your API consumers don't have to repeat it. E.g. .grid-define(@breakpoint) {
.grid-gutters(@width) {
#grid-internal > .grid-gutters(@breakpoint, @width);
}
}
#grid-internal {
.grid-gutters(@breakpoint, @width) {
@media screen and (min-width : at(@breakpoints, @breakpoint)) {
& {
margin-left : (-.5 * @width);
margin-right : (-.5 * @width);
}
& > &col {
padding-left : (.5 * @width);
padding-right : (.5 * @width);
}
}
}
} used as: .my-grid {
.grid-define(small);
.grid-gutters(10px);
} Helps a lot if you find you have to define more stuff, like entire systems for shifting/swapping/pushing/pulling columns to/from left or right in various slot distributions. |
I actually have a really good example. I am creating a CSS framework on top of LESS & eventually SASS. My framework uses 600+ LESS variables exposed to the user specifically used for colors. So the user includes my framework.less in their own theme.less file, then they set up the default values for the 600+ variables in their own .defaults() function. then it gets tricky. Each section of their web page can use a different theme, so their header might use a dark theme, body might use a light theme, side bar might use a blue theme, so they'll have to set up 600+ variables for each section of their website, and then they call a .render() function to render the CSS for each section of their website using the provided variables. This is how I want the workflow for my users to be like:
Now this is where dynamic variable names come to play If one section in their LESS file uses different colors for a button compared to the default colors, they'll have to change 15 different variable names, just for 1 button type (types include default, apply, cancel, disabled, special, etc). To change all 6 different button types, they'll have to change 90 different variables. Instead, I want them to execute a function that modifies the default variables values for a specific button type before they call the .render() function. for example:
this allows them to focus on generating different themes for different sections of their web page without having hundreds of lines of LESS, just to change the colors of their theme. Of course, the .button() function can't use dynamically generated variables at the moment, so instead I use an if statement and hardcoded the 6 different button types with 15 variable names for each type
what I want to do instead is find a way to just change
to
In the future, I would want them to purely use functions to change the different parts of their theme before rendering the theme for example:
the key concept here is that the user sets up global variables then executes the .render() function to render all the elements of the theme in the correct order. |
I'm not convinced. Yet again see my example there. Your example shows nothing but general fail of any frameworks using armies of global variables named like So yet again see less/less-meta#12 for the proper namespacing improvement proposal. (Doh! In other languages they considered global variables to be harmful yet in 1970s. I can understand how that |
@markentingh I was going to suggest another option based on scoped, matching mixins. But after I wrote up my example, I ran into this bug: #2984. Like @seven-phases-max said, we will probably close this issue not because variable referencing / overriding doesn't need improvement, but because a better syntax for addressing some of those things is already on the table. |
it's alright. Perhaps my whole approach to my framework is wrong to begin with. Keep up the good work, guys. |
Renaming the ticket to accommodate both var/mixin cases since they almost always come together as the "Attempting to simulate namespaces and/or parameters via string based manipulation of identifiers" anti-pattern. |
I think there is a merit here, although not for dynamic variables. See this example: @color-transformation: lighten; |
Storing function names inside variables is highly ambiguous. There is no way to discern a keyword from a function name in the parsing phase and the presence of plugins and plugged in functions makes it highly problematic during the evaluation phase as well. Your particular case will eventually be covered by mixins being able to return values and being able to be passed by reference, like detached rulesets. The pairing of those two features would give Less the ability to create lambdas. A possible future syntax for that might be: .some-mixin(@color-transform) {
@some-var : @color-transform(red);
}
// ...
.color-transform(@color) : @return {
@return : lighten(@color, 10%);
}
.some-other-mixin(.color-transform); But it's all still being debated and conceptualized. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
If you don't give us the option to create functions like in Sass then at least let us do dynamic variable names, this is so frustrating, I'm trying to write a mixing to calculate my em size based on the font-size in px and I can't return a custom value to be used in multiple places, so for example if I have:
SO I have to create a duplicated mixin just because I can't have a custom returned variable name? At least give us the ability to create functions that automatically return a value, something like this:
I also don't want to write the long version of the property just because it's against the coding style we're using. |
You have it for more than 2.5 years by now.
What is the point of asking to support a dirty kludge instead of asking to support a proper solution instead? |
I was expecting it to be built-in and I didn't know about the plugin, my apologies. I would suggest adding this to the core as it's something that a lot of people are using. |
For the particular use-case though, I believe the proper solution would be a an arbitrary-unit-conversion-plugin (autoconverting whatever units (incl. virtual) back and forth depending on its settings), so one would simply write something like: |
Agree with @seven-phases-max Validate that |
@rjgotten this is what I ended up with:
|
Closing since there a few solutions suggested. |
Using less 2.5.1 on http://less2css.org I can use variable variable Names to access a variable.
Outputs:
However when I try to declare the varible the same way, it gives an error:
Gives:
The real world use case for this is I would like to have a grid framework that supports a variable number of breakpoints the user can change the names of. So I need to take a list of screen size names and declare a bunch of variables based on those. i.e.
And then use mixins to declare variables like
@screen-s-min
,@screen-m-max
etc.The text was updated successfully, but these errors were encountered: