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

Feature request: Property lookup (a.k.a Properties are Variables too) #2433

Closed
krnlde opened this issue Feb 3, 2015 · 58 comments
Closed

Feature request: Property lookup (a.k.a Properties are Variables too) #2433

krnlde opened this issue Feb 3, 2015 · 58 comments

Comments

@krnlde
Copy link

krnlde commented Feb 3, 2015

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?

@SomMeri
Copy link
Member

SomMeri commented Feb 3, 2015

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).

@krnlde
Copy link
Author

krnlde commented Feb 3, 2015

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.

@krnlde
Copy link
Author

krnlde commented Feb 3, 2015

Another example would be alert boxes:

.alert {
  color: #f00;
  background: lighten($color, 25%);
}

@Justineo
Copy link
Contributor

Justineo commented Feb 3, 2015

I think it's not necessary but nice to have. Variables can do the job but direct property references are more readable.

@krnlde
Copy link
Author

krnlde commented Feb 3, 2015

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.

@matthew-dean
Copy link
Member

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.

@krnlde
Copy link
Author

krnlde commented Feb 3, 2015

I see the concern to blow up the language too much so it might not be considered "sleek" anymore though.

@lukeapage
Copy link
Member

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

@krnlde
Copy link
Author

krnlde commented Feb 4, 2015

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.

@matthew-dean
Copy link
Member

@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.

@lukeapage
Copy link
Member

lukeapage commented Feb 4, 2015 via email

@lukeapage
Copy link
Member

lukeapage commented Feb 4, 2015 via email

@matthew-dean
Copy link
Member

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?

@krnlde
Copy link
Author

krnlde commented Feb 4, 2015

I like ${color}.

@matthew-dean
Copy link
Member

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.

@seven-phases-max
Copy link
Member

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 []?

@seven-phases-max seven-phases-max changed the title Feature request: Property lookup (from Stylus) Feature request: Property lookup (a.k.a Properties are Variables too) Feb 4, 2015
@matthew-dean
Copy link
Member

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:

  • Step 1: Support properties as "vars" (with possible exceptions)
  • Step 2: Address @krnlde's second example by simply supporting namespace references rather than "local" selectors: ${#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.

@krnlde
Copy link
Author

krnlde commented Feb 4, 2015

What about §, /and \?

@krnlde
Copy link
Author

krnlde commented Feb 4, 2015

\ 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.

@matthew-dean
Copy link
Member

Is the § easily accessible on English keyboards? I don't have one to verify atm.

Er, nope. No idea what that is.

@krnlde
Copy link
Author

krnlde commented Feb 4, 2015

It's a paragraph sign in (German) law

@matthew-dean
Copy link
Member

Ah, right, I think I've seen that then. Regardless, it's not on the keyboard.

@krnlde
Copy link
Author

krnlde commented Feb 4, 2015

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

@seven-phases-max
Copy link
Member

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 :)

@matthew-dean
Copy link
Member

@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.

@matthew-dean
Copy link
Member

That said, because this might be a new symbol / reference method, I think it's worth getting a lot of input / consensus for it.

@kuus
Copy link

kuus commented Feb 4, 2015

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

@seven-phases-max
Copy link
Member

@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 ifs)).

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? :)

@SomMeri
Copy link
Member

SomMeri commented Feb 6, 2015

@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.

@kuus
Copy link

kuus commented Feb 6, 2015

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 #2433 (comment) no matter what it compiles to, it's too convoluted.

@krnlde
Copy link
Author

krnlde commented Feb 6, 2015

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).

@seven-phases-max
Copy link
Member

@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... ;)

@seven-phases-max
Copy link
Member

@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 #2212 (comment) (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...

@matthew-dean
Copy link
Member

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.

@matthew-dean
Copy link
Member

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;
}

@matthew-dean
Copy link
Member

@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;
}

@ase69s
Copy link

ase69s commented Mar 10, 2015

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...

@matthew-dean
Copy link
Member

@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.

@ldetomi
Copy link

ldetomi commented Oct 9, 2015

+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."

@jlem
Copy link

jlem commented Oct 23, 2015

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.

@matthew-dean
Copy link
Member

@jlem There's already a PR in progress (that I worked on - #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.

@stale
Copy link

stale bot commented Nov 14, 2017

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.

@stale stale bot added the stale label Nov 14, 2017
@stale stale bot closed this as completed Nov 28, 2017
@matthew-dean
Copy link
Member

matthew-dean commented Nov 28, 2017

Note that simple property referencing ($prop) is implemented, but not documented (AFAIK).

@olets
Copy link

olets commented May 13, 2024

For others who like me find this issue before the relevant docs: this was added to the documentation in 2018 https://lesscss.org/features/#variables-feature-properties-as-variables-new-

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

12 participants