I found a pretty neat feature in Stylus recently, a property lookup which allows you to use properties in the current or closest parent ancestor and use it for calculations.
In Stylus this would look like this:
.test
foo: 200
bar: (@foo/2)
.child
baz: @foo
which produces this CSS:
.test {
foo: 200;
bar: 100;
}
.test .child {
baz: 200;
}
For less we could use the $-sign or maybe brackets to select properties. It then would look something similar to this:
.test {
foo: 200;
bar: $foo / 2;
.child {
baz: $foo;
}
}
or this respectively:
.test {
foo: 200;
bar: [foo] / 2;
.child {
baz: [foo];
}
}
Thoughts on this?
What is your use case? In current less.js, you can place foo value into variable and achieve something similar.
This:
.test {
@foo: 200;
foo: @foo;
bar: @foo / 2;
.child {
baz: @foo;
}
}
compiles into:
.test {
foo: 200;
bar: 100;
}
.test .child {
baz: 200;
}
If you have a case where such refactoring is not possible, it would be helpful if you would posted it here. Generally speaking, less.js adds new features when some use case is impossible or too cumbersome to do. Use case would help us to decide how much is such feature needed (or we could find another way how to achieve what you wants).
The usecase would be that you can build up semantic contexts, for example:
@gutter-width: 30px;
.row {
margin-left: -(@gutter-width / 2);
margin-right: -(@gutter-width / 2);
.col-half {
width: 50%;
float: left;
padding-left: -$margin-left;
padding-right: -$margin-right;
}
}
Since in bootstrap the margin is dependent on the padding and vice versa, why not connecting them directly rather than just using the same variable?
You would delete multiple variable references and build up a more coherent module that relies on its context rather than just variables. The guys from Stylus obviously see a usecase for this otherwise they would have deprecated this as a relict.
Another example would be alert boxes:
.alert {
color: #f00;
background: lighten($color, 25%);
}
I think it's not _necessary_ but _nice to have_. Variables can do the job but direct property references are more readable.
Of course it is not necessary, at all. We worked with variables for years and it's awesome. Especially the lazy loading behaviour every other Pre-Processor is lacking. Nonetheless I wanted to post this for consideration.
I agree that something like this would be intuitive. A lot of times I have an initial value, set for a property, and if I want to use that value elsewhere, it has to be refactored into a variable and then re-added to the property. This proposal would be simpler for authors.
There have been similar requests to have parts of the immediate Less tree accessible as structured data, so I'm for having more "accessors" to what you've already written.
I see the concern to blow up the language too much so it might not be considered "sleek" anymore though.
Related to #76 and #6 - though this approach is simpler. It does save on useless code assigning to a variable and then reading from it
Reading #76 I found some similarities which might be combined, say if you background-color: lighten([color], 25%);
it would lookup the current ancestor tree. However if you write background-color: lighten(#mySpecialSelector[color], 25%);
it'll instead use the ancestor tree of the selector.
That would really be useful and opens a whole new level of opportunities.
@lukeapage What do you think the code impact would be? If local (current tree) properties could be shimmed to behave similar to local vars, it seems like it would be not that major (but you would know better). It could add a certain elegance to the language, since people could do:
.rectangle {
width: 200px;
height: ([width]/2);
}
Instead of:
.rectangle {
@width: 200px;
width: @width;
height: (@width/2);
}
The first example seems easier to read, and feels closer to the intention of the author. I personally have written code like the second example many times, so I like this idea.
For local only lookups, i think the cost is small. Adding selectors might
lead to more parse complexity. Doesnt css columns use square brackets in
values?
No, not css columns, will have to think where i saw it.
Attribute values use square brackets, so if we're including selectors in the property lookup, then square brackets wouldn't work, because this is a valid selector:
#mySpecialSelector[color] {
}
// matches:
<div id="mySpecialSelector" color></div>
The square brackets match an element that has the attribute "color". We could be more explicit, with something like prop().
.rectangle {
width: 200px;
height: (prop(width)/2);
}
However, I know in the past we've tried to avoid nested parentheses hell, so... what if we combine the $
with our interpolated variable syntax?
.rectangle {
width: 200px;
height: (${width}/2);
}
.example {
background-color: lighten(${color}, 25%);
background-color: lighten(${color, #mySpecialSelector}, 25%);
}
Something like that? That way attribute selectors are valid:
.parent[id] {
height: 50%;
.child {
height: (${height, .parent[id]} / 2);
}
}
What do you guys think?
I like ${color}
.
Actually, if we thought of ${}
or whatever syntax as a "tree reference", we could address issues like #1075 with the same syntax:
.table .row .cell {
${.row}.editing {} // .table .row.editing .cell {}
}
...but I don't want to confuse things. Just an idea.
For non-local lookups it's directly tied to #1848 (I guess the tricky part is only in crafting consistent syntax, internally in the tree variables and properties are handled almost identically so anything implemented for the first is quite easily extended to work with the second and vice-versa).
However if you write background-color: lighten(#mySpecialSelector[color], 25%); it'll instead use the ancestor tree of the selector.
This way #mySpecialSelector
can't have any ancestors (because if it has then it has to be referenced with *ancestor(s) #mySpecialSelector
. I.e. just #mySpecialSelector
may match only top level selector in the global scope (and/or optionally an element with such name somewhere upwards current scope). But yet again, I think the namespacesing part is better to discuss in #1848 (the differences between variables and properties there are supposed to be only in syntax (e.g. selector[@variable]
and selector[property]
) _if_ this one is implemented).
Btw., any ideas beside $
and []
?
I was thinking about namespacing as I was mulling this over. Technically, this is different because we're (currently) talking about support for references in the local tree. We could forego selector lookups initially and, as you said, treat them like variables. If the property doesn't exist locally, go to the next parent block, and so on. We could, in fact, just pretend that properties are variables, albeit they need to be referenced in a different way. So, if we don't support referencing namespaced vars, then I think we wouldn't initially support namespaced properties. But we could support imported properties via mixin, since it would be the same as what variables do. In fact... don't we essentially do that already, since later properties will override previous ones?
I would love to reference namespaced vars / properties as well. It would be amazing. And, like #1075, we may be close to a syntax that is flexible enough to point to any value anywhere in the AST. But we wouldn't have to implement it all at once.
As far as special selectors, I know we've tried to avoid introducing extra symbols in these issues, but I feel like we've been jumping through hoops in #1075 and #1848 to not do so. For example, in #1848, at the end I mention this syntax:
.box:after {
content: "@{#ns > @content}";
}
But, of course, the extra @
at the beginning only makes sense if I'm referencing a namespaced variable. It doesn't semantically translate to a property reference, however, while an additional symbol that is a "lookup" is more flexible:
// property
.box:after {
content: "${#ns > content}";
}
// or a variable
.box:after {
content: "${#ns > @content}";
}
So, maybe we could kill two (possibly 3) birds with one stone and break up the tasks like this:
${#mySpecialSelector > color}
As far as alternatives to $
, I think we should be limited to the shift key and the number row. We can't use !
, @
, #
, &
, *
, ~
, ``, as it either conflicts with CSS or Less. That leaves
$,
%,
^. I suppose there's a few more symbols on the keyboard, but if we're going to add one,
$` would be my first choice.
What about §
, /
and \
?
\
is used for Unicode characters. The rest should be fine. Is the §
easily accessible on English keyboards? I don't have one to verify atm.
Is the
§
easily accessible on English keyboards? I don't have one to verify atm.
Er, nope. No idea what that is.
It's a paragraph sign in (German) law
Ah, right, I think I've seen that then. Regardless, it's not on the keyboard.
Alright. As I figured out by fiddling around, /
is not only cumbersome but also unusable since it is the divide operator in less, so padding-top: /height//line-height;
wouldn't work anyways. So I guess we stick with $
for now? Like padding-top: $height/$line-height;
. Looks nice
Doh, I'm afraid I would vote for $
too because it seems to be the most clean and the least conflicting (though honestly I really hate it (for no particular reason) and because of that I feel pain everytime I have to write something in PHP :)
@seven-phases-max - Haha, I had the same visceral reaction the first time it was brought up, for the same reasons. However, I've become more pragmatic lately, and twisting existing symbols to mean more things (as we've been toying with in regards to @
and &
) in order to avoid adding new ones, or in order to restrict ourselves to CSS's symbol set may be unnecessary gymnastics, and not necessarily the easiest-to-grasp solution. I think it's easier for a user to understand symbols if they have more distinct behaviors. And CSS doesn't have certain behaviors that we're representing, so in some cases it may be unwise to co-opt / repurpose CSS's symbols. That probably contradicts a lot of what I've said in the past, but like I said, I'm not sure such strictness has always been necessary.
And also, relative to your point, if we expand an existing symbol definition, we may back ourselves into a corner if we want to further expand the usage.
That said, _because_ this might be a new symbol / reference method, I think it's worth getting a lot of input / consensus for it.
I understand that being able to do this:
.rectangle {
width: 200px;
height: ${width} / 2;
}
could be nice but this:
.example {
color: lighten(${color, #mySpecialSelector}, 25%);
}
is uselessly complex to read and manage. If someone would give me a piece of code like this to work on it would very tedious and complex to find out which is the actual color to be lighten
.
To me if feels more or less like in javascript setting a property of a DOM node in this way:
var el1 = document.createElement('div');
var el2 = document.createElement('div');
el1.className = document.querySelector('mySpecialSelector').id + '25percent';
el1.className = document.querySelector('mySpecialSelector').id + '10percent';
instead of:
var CLASS_NAME = 'my-color-class';
var el1 = document.createElement('div');
var el2 = document.createElement('div');
el1.className = CLASS_NAME + '25percent';
el1.className = CLASS_NAME + '10percent';
I don't know if I gave the idea of what I mean. But I think all this thing is weird, at least for me and I wouldn't use it. Overall I think variables are a good and solid abstraction that already cover all this cases. Making the language more complex doesn't make it more powerful. And make it less readable doesn't help either.
my2c
this:
.example { color: lighten(${color, #mySpecialSelector}, 25%); }
is uselessly complex to read and manage.
I agree. That's why I think that piece is probably worthy of moving to the namespace reference thread (#1848), as @seven-phases-max suggested, and referencing in a namespace format anyway. For this particular thread, the only thing on the table should be referencing property values.
@kuus, @matthew-dean agreed on that. Might be to much.
What about mixins? Should they look up _their_ closest parent or that tree where they will be placed in? Can't give an example atm, I'm on mobile - will give that later if necessary.
@kuus Mixins looks for variables in their own parent first. If search is not found, search continues where it is placed in. I think that properties should work the same way: search mixin definition first and caller second.
:+1:
So to sum up the current state of the discussion:
${width}
though $width
would be shorter and former has no advantages (might need futher discussion as we progress).height: ${width, .my-selector}
or height: .my-selector${width}
.What about the beloved "lazy-loading" of Less-variables? What influence has this knowledge for properties?
@krnlde
By the moment I would limit the list only to: $width
- refers to a width
property searching from the current scope upwards.
(Namespacing and ${...}
syntax need further discussion).
By the moment I would limit the list only to: $width - refers to a width property searching from the current scope upwards.
Agreed. I support doing both syntaxes, so it's logical to start with the more "limited" variation, and nail down ${}
after that.
Incidentally @krnlde, I now support the namespaced version _instead of_ the "within tree" selector lookup, as that seems more flexible, but let's start with $property and go from there.
So far so good. How do we proceed?
Well, looks like we have sufficient buy-in from contributors, so I feel we can mark it ready for implementation, unless a compelling objection comes up.
On Feb 5, 2015, at 12:20 PM, Kai Dorschner [email protected] wrote:
So far so good. How do we proceed?
—
Reply to this email directly or view it on GitHub.
Why &
is off the table? AFAIK, Less isn't using &
except in selectors. And plus, I think &
is better to stand for reference.
AFAIK, Less isn't using & except in selectors.
Yes by now. But we should not steal symbols from other possible features (even if they are too far).
Currently, &
is a concatenation operator for selectors and isn't being used for any lookup. This issue is about property references and treating them like variable references, so as it stands right now, the concepts are pretty far apart.
&
is used as reference in some programming languages like C++. I agree more on Max's point that we could use it like Sass does now in the future:
.foo.bar .baz.bang, .bip.qux {
$selector: &;
}
@seven-phases-max I think that we should make it search mixin scope first from the start. Language is easier to learn and read if it behaves consistently. Less scoping has too many rules to remember (imo) already, adding new ad hoc one will not make it easier.
It easier to say "there is scoping and scoping works like this" then have slightly different scoping for every feature (detached ruleset, properties, variables, mixins lookup). Changing scoping later on is harder then making it the same as already existing one from start.
I think that this:
#namespace() {
padding-right: 2;
.miniPadding() {
padding-left: $padding-right - 1;
}
}
.big-table {
padding-right: 5;
.sub-table {
#namespace() > .miniPadding();
}
}
should compile to:
.big-table {
padding-right: 5;
}
.big-table .sub-table {
padding-left: 1;
}
compiling it to following would be just confusing/unexpected and look like bug:
.big-table {
padding-right: 5;
}
.big-table .sub-table {
padding-left: 4;
}
Plus, if variables and property scoping behaves the same way, then we might be able to use exactly the same structures and code to handle them - and most bugs in one will be fixed in another too. Two different scopings (or three when you count in detached rulesets differences) means maintaining two similar but somewhat different algorithms which is harder.
You describe a normal closure behavior. Makes sense to me.
@SomMeri
I think that we should make it search mixin scope first from the start.
But I wrote exactly the same :) (Upd:. Err, I mean "I meant property lookup rules should be exactly the same as those of variables", (more over I know they already _are_ the same within the compiler because it is the same code that handles variables and properties (with a few minor if
s)).
searching from the current scope upwards.
is an exact quote from Variables > Lazy-Loading.
Speaking of your example it's quite unhappy. Because the same example with variables steps into #1316 and the result is padding-left: 4;
. I understand why you used #namespace()
there but with all of "parametric namespace" issues it can be quite confusing. We need some other example (because your one is exactly where it's better to use variables instead of properties).
P.S. Strange thing: for the last three days it's four issues directly related to #1316 popped up (#2435, #2350, #2436 and now your example) Is it some kind of conspiracy? :)
@seven-phases-max I misunderstood you and forgot about #1316. Sorry about both :).
All those four issues are the same underlying problem? Is it easy/hard to fix in your opinion? I do not want to do major change in less.js yet (know too little about its scoping), but I am looking for something easy to medium hard to fix.
Am I the only one who thinks that all this thing is unnecessary and makes the language more complicated and less readable? What is the exact benefit you're looking for?
When I read a style declaration I don't want to go crazy to understand where the property value is coming from. Like now it has to be a normal value or a variable, or maximum an inline calculation that uses variables. That's it. This makes css preprocessors a pleasure to use. What you're proposing is a kind of dynamic way to define variables, which is just weird. I don't how javascript developers can not agree with the fact that is weird.
Definitely I agree with @SomMeri when he says
Less scoping has too many rules to remember (imo) already"
And I think his sample code https://github.com/less/less.js/issues/2433#issuecomment-73202522 no matter what it compiles to, it's too convoluted.
Definition from Stylus:
Property Lookup
Another cool feature unique to Stylus is the ability to reference properties defined without assigning their values to variables. A great example of this is the logic required for vertically and horizontally center an element (typically done using percentages and negative margins, as follows): ...
So to capture the usecase I described earlier you could shorten definitions by saving variables, plus it is more concise since you connect the properties directly rather than over variables:
.mySquare {
width: 200px;
height: $width;
}
.myRectangle {
width: 500px;
height: $width / 2;
}
Just by reading height: $width;
everyone knows exactly what's going on there - it is a square, no matter what.
The nesting-discussion and whether or not it should lookup all the parent selectors should be discussed elsewhere (#1848).
@kuus
Well, my involvement is only limited to hanging around and screaming if some proposed syntax/behaviour gets out of control :) Actually I'm neither for nor against it (I won't mind it because it was flying around from the earliest Less days (it was even (partially?) implemented in the initial Ruby version) and I know it should not burden compiler unless we start to invent insane syntactic constructions... Also I know that the fact a feature gets its "ReadyForImplementation" does not mean it actually to be implemented soon or ever... ;)
@SomMeri
All those four issues are the same underlying problem? Is it easy/hard to fix in your opinion?
Yes, it's the same underlying problem but in most cases it's more about different expectations of how these things should work (of how scoping should work and how it works actually) and not really the code that suffers from #1316 . We discussed possible ways to fix #1316 itself around https://github.com/less/less.js/issues/2212#issuecomment-57281241 (not so easy but probably possible) but those issues (half-issues/half-feature-requests) I mentioned (except your example above of course) are actually asking to fix it in opposite way or provide a backdoor for that (thus breaking current Less behaviour, the good example is #2435).
@krnlde I'm sorry for mismentioning...
When I read a style declaration I don't want to go crazy to understand where the property value is coming from. Like now it has to be a normal value or a variable, or maximum an inline calculation that uses variables. That's it. This makes css preprocessors a pleasure to use. What you're proposing is a kind of dynamic way to define variables, which is just weird. I don't how javascript developers can not agree with the fact that is weird.
I think you're probably misunderstanding. There's nothing proposed here that is a dynamic way to define variables. It actually seems like a very intuitive step for a few reasons.
Variables in Less came directly from (or was inspired by) the behaviour of properties. In some preprocessors, if you assign a variable, then read it, then assign it, then read it, all within the same scope, you'll get a different result. In Less, the behavior is like CSS properties: the latest declaration in the scope wins. So, right now, variables and properties have a lot of overlap. If you call a mixin, you get not only its properties but its variables. And you can actually do things like add properties together (or rather, add its values) with the property+:
and property+_:
syntax. The one thing you can't _yet_ do is reference the property.
So, variables are meant to behave like properties, and properties have evolved to behave a lot like variables. In the end, you're assigning a value to a key. We have a way to reference the value for one type of key, and what's proposed here is simply a way to reference the value for the other type of key.
Less scoping has too many rules to remember (imo) already
There wouldn't be any additional scoping rules to remember. These two blocks would become functionally equivalent as far as scoping.
.block {
@height: 10px;
width: @height / 2;
}
.block {
height: 10px;
width: $height / 2;
}
The difference is that height is output as a property in the second block. But scoping does not change.
Or the TL;DR way to say it: properties in Less, by and large, already act like variables, but you cannot reference them. This feature would allow property referencing.
Another way to demonstrate the above:
//Less
.block {
@height: 10px;
width: @height / 2;
@height: 20px;
}
// output
.block {
width: 10px;
}
//Less w/ property reference
.block {
height: 10px;
width: $height / 2;
height: 20px;
}
//output
.block {
width: 10px;
height: 20px;
}
@krnlde's example inspired another demo:
.square(@width) {
height: @width;
}
@row: 100px;
.column-2 {
width: @row / 2;
.square($width);
}
.column-4 {
width: @row / 4;
.square($width);
}
// output
.column-2 {
width: 50px;
height: 50px;
}
.column-4 {
width: 25px;
height: 25px;
}
This is a useful feature, in my case i needed to extend only some specific properties from another class (that class is defined in an external stylesheet so in order to use variables i would have to modify that stylesheet in each new version they publish)
With this new syntaxis i could do it like this:
The stylesheet.less provided from another source:
.divheader{
height:50px;
color: red;
line-height: 1.5;
width: 100px;
}
My stylesheet.less:
.mydivheader{
.divheader.height; //invented syntax
.divheader.line-height; //invented syntax
color: black;
}
//output
.mydivheader{
height:50px; //inherited
line-height: 1.5; //inherited
color: black;
}
So i only inherit the height and line-height properties of the divheader class...
@ase69s In your case we would need to address referencing variables within rulesets. See #1848. Essentially, this feature would make property assignment behave (somewhat) like variable assignment (albeit with different referencing syntax), so your case wouldn't happen before #1848 is addressed.
If / when implemented, I would see the syntax being a little closer to this:
.divheader{
height: 50px;
color: red;
line-height: 1.5;
width: 100px;
}
.mydivheader {
height: ${.divheader > height};
line-height: ${.divheader > line-height};
color: black;
}
However, since you're not calculating anything, there are a few other options available to you right now, such as:
@divheader: {
height: 50px;
line-height: 1.5;
};
.divheader{
@divheader();
color: red;
width: 100px;
}
.mydivheader {
@divheader();
color: black;
}
You could also have a mixin that sets properties, or use :extend
on a common ruleset. So rather than directly referencing another class, you could have two divs reference another detached ruleset or mixin.
+1 I completely agree with Justineo:
"_I think it's not necessary but nice to have. Variables can do the job but direct property references are more readable._"
I'd say this is more than nice to have. I have a site that lets you build skins by picking a trim color that controls buttons, links, and misc elements. This is stored in a database, and gets bootstrapped onto the page via a <style>
tag in the header as the class "trim". But because this value is hard-coded, I have no easy way to reference it to do things like create faded versions of that same color on demand, and I'd rather not use JS for styling...
As it stands now, I'll have to manually pick off-shade variants of the trim color and assign those to classes as well, but even then, I don't have very easy control over WHICH properties that faded trim color would get applied to (e.g. borders), inner backgrounds etc.
@jlem There's already a PR in progress (that I worked on - https://github.com/less/less.js/pull/2654), and basically working, but there are a few small issues to iron out. Interesting use case, but that wouldn't apply here, as the other styles would not be accessible by Less.
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.
Note that simple property referencing ($prop
) is implemented, but not documented (AFAIK).
Most helpful comment
Note that simple property referencing (
$prop
) is implemented, but not documented (AFAIK).