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 for conditional values #1894

Closed
skyshab opened this issue Feb 22, 2014 · 19 comments
Closed

Feature request for conditional values #1894

skyshab opened this issue Feb 22, 2014 · 19 comments

Comments

@skyshab
Copy link

skyshab commented Feb 22, 2014

I often find myself needing to apply one style if an argument is x, and another style if y .

The addition of CSS Guards in 1.5.0 made it easy to wrap a group of styles like so:

.my-thing {
    & when (@my-option = true) {
        color: white;
        background: black;
        display: block
    }
}

However, the syntax seem excessive when it's just one or two attributes in a style block. In PHP and JS, I am accustomed to the ternary operator syntax, and it seemed like it might easily fit into the Less language. Some examples:

@my-value: true;

.my-thing {
    // if @my-value evaluates to true, then black. else, white.
    color: (@my-value = true) ? black : white;
}

@my-other-value: false;
@my-color: (@my-other-value = true) ? black : white;

.my-other-thing {
   color: @my-color;
}

// outputs

.my-thing {
    color: black;
}

.my-other-thing {
    color: white;
}

Any thoughts?

@skyshab
Copy link
Author

skyshab commented Feb 23, 2014

To add an example of why I'm suggesting this:


@myvar: #edba1d;

// current way

.my-callout-box {
    & when ( lightness(@myvar) >= 60%) {
        background: black;
    }
    & when ( lightness(@myvar) < 60%) {
        background: white;
    }    
}

// with what I'm suggesting

.my-callout-box {
     background: ( .lightness(@myvar) >= 60% ) ? black : white;
}

@seven-phases-max
Copy link
Member

For the last example see constrast.
And in general such things are better coded in more high-level way, e.g.:

.config(true);

.config(true) {
    @fore-color:    black;
    @back-color:    white;
    @whatever:      red;
    @whatever-else: 20px;
    // etc.
}

.config(false) {
    @fore-color:    white;
    @back-color:    black;
    @whatever:      blue;
    @whatever-else: 40px;
    // etc.
}

.my-thing {
    color:            @fore-color;
    background-color: @back-color;
    width:            @whatever-else;
}

.my-other-thing {
    color:            @whatever;
    background-color: @back-color;
    margin:           @whatever-else;
}

It's not that you never need per-property conditions (sometimes you do) but if you need them too often then it's definitely something wrong with the approach you use. After all these are just style sheets not a behavioural script so I doubt this proposal will get too much support. Ternary operator looks too alien for the CSS. Maybe it could be some kind of function like select(%condition%, @true-value, @false-value) but for the moment Less does not allow to use comparison operators in a variable or function/mixing parameter statements.

@lukeapage
Copy link
Member

also note: your current example is probably a bad use-case for using &

e.g. this is shorter (though I do admit it duplicates the selector in the less)

@myvar: #edba1d;

// current way

.my-callout-box when ( lightness(@myvar) >= 60%) {
    background: black;
}
.my-callout-box when ( lightness(@myvar) < 60%) {
    background: white;
}

@lukeapage
Copy link
Member

p.s. but I agree @seven-phases-max approach is even better

@lukeapage
Copy link
Member

I can see a small use-case for this, but its quite a lot of sugar for a little benefit. It has come up before but I can't find the previous requests.

@skyshab
Copy link
Author

skyshab commented Feb 23, 2014

Well, my example was contrived, but the point was multiple logical decisions within a single selector without having to call out to yet another mixin.

Even if there was support for this, I'm afraid it would be still be limited. For example, what if there was no 'else', or the value was set as null. Doesn't seem there would be a way to avoid outputting the property, so you would wind up with "property: ;", or "property: null;"

FWIW, I wrote the following mixin, which gives all sorts of control on a per property basis.

// if then
.if( @property: null; @if: null; @is: e; @to: true; @then: null; @else: null; @filter: null; ) 
    when 
    (@is = e) and (@if = @to),
    (@is = ne) and not (@if = @to),
    (@is = gt) and (@if > @to),
    (@is = ge) and (@if >= @to),
    (@is = lt) and (@if < @to),
    (@is = le) and (@if <= @to)
{
    .filter(@property; @then; @filter);
}

// else then
.if( @property: null; @if: null; @is: e; @to: true; @then: null; @else: null; @filter: null; ) 
    when 
    (@is = e) and not (@if = @to),
    (@is = ne) and (@if = @to),
    (@is = gt) and not (@if > @to),
    (@is = ge) and not (@if >= @to),
    (@is = lt) and not (@if < @to),
    (@is = le) and not (@if <= @to)
{
    .filter(@property; @else; @filter);
}

// filter
.filter(@property: null; @value: null; @filter: null) 
    when 
    not (@property = null) and not (@value = null) and (@filter = null),
    not (@property = null) and not (@value = null) and (@filter = isnumber) and ( isnumber(@value) ),
    not (@property = null) and not (@value = null) and (@filter = isstring) and ( isstring(@value) ),
    not (@property = null) and not (@value = null) and (@filter = iscolor) and ( iscolor(@value) ),
    not (@property = null) and not (@value = null) and (@filter = iskeyword) and ( iskeyword(@value) ),
    not (@property = null) and not (@value = null) and (@filter = isurl) and ( isurl(@value) ),
    not (@property = null) and not (@value = null) and (@filter = ispixel) and ( ispixel(@value) ),
    not (@property = null) and not (@value = null) and (@filter = isem) and ( isem(@value) ),
    not (@property = null) and not (@value = null) and (@filter = ispercentage) and ( ispercentage(@value) )
{
    @{property}: @value;
}

// calling the mixins

@myval: 18px;
@myswitch: true;
@mything: null;
@my-other-thing: #bada55;

#my-thing {

    .if( color;
        @if: @myswitch; 
        @then: black; 
        @else: white;
    );

    .if( width;
        @if: @myval;
        @is: le;
            @to: 16px;
            @then: 16px;
        @else: 32px;
        @filter: ispixel;   
    );

    .filter(background; @mything ); // outputs nothing
        .filter(background; @my-other-thing); // outputs the color
}

// outputs

#my-thing {
    color: black;
    width: 32px;
    background: #bada55;
}

@skyshab
Copy link
Author

skyshab commented Feb 23, 2014

Of course, it's much easier when you have groups of rules that are all dependant on a single value to just use CSS Guards.


.my-thing when (@my-option ) {
        color: white;
        background: black;
        display: block;
}

.my-thing when not (@my-option) {
        color: black;
        background: white;
        display: block;
}

Sometimes things are more complicated than that however, and adding in 3 or more rules that each has their own logic can turn an otherwise simple declaration block into spaghetti.

@seven-phases-max
Copy link
Member

3 or more rules that each has their own logic can turn an otherwise simple declaration block into spaghetti.

It is spaghetti by definition with or without ternary operator (e.g. "spaghetti code" is not counted by number of written lines but by the number of conditions used).

@skyshab
Copy link
Author

skyshab commented Feb 23, 2014

@lukeapage

"also note: your current example is probably a bad use-case for using &"

Curious as to why you say that?

.my-callout-box {

    & when ( @myvar ) {
        background: black;
    }
    & when not( @myvar ) {
        background: white;
    }   

    // static rules 
    width: 100%;
    margin-bottom: 1.5em;
}

This gives you one declaration block without having to repeat any rules. What you suggest would have me make a separate declaration for the static rules, or repeat them in both blocks. Is there a reason those options would be preferable? Am I missing something?

@seven-phases-max
Copy link
Member

Btw. if it comes to hacks and workarounds it is possible to simplify the .if thing just to .set(background, @x lte @y, black, white) and .set(background, iscolor(@somevar), black). You can use pattern-matching instead of endless whens in your implementation.

@skyshab
Copy link
Author

skyshab commented Feb 23, 2014

@seven-phases-max

Btw. if it comes to hacks and workarounds it is possible to simplify the .if thing just to .set(background, @x lte @y, black, white) and .set(background, iscolor(@somevar), black). You can use pattern-matching instead of endless whens in your implementation.

Example mixin? Not sure I see how to use pattern matching here.

Update: Specifically, how would you evaluate "@x lte @y" in your example?

@seven-phases-max
Copy link
Member

Example mixin?

Something like this (I did not bother with too deep testing so it may have some bugs).

@skyshab
Copy link
Author

skyshab commented Feb 23, 2014

Wow, thanks! I'll have to study that more in depth. I'm not experienced with extracting values like that; looks like a great tool to have available.

Really appreciate the feedback guys.

@skyshab
Copy link
Author

skyshab commented Feb 23, 2014

Didn't realize you could pattern match any of the arguments. Thought it was only the first argument for some reason. Mind blown. Also, didn't know you could extract items that weren't separated by commas. Thanks again!

@skyshab
Copy link
Author

skyshab commented Feb 24, 2014

So people may not be for my original request, what about simply allowing to assign a query as a value to a var?

@my-bool: (@some-value > 24);

#my-thing {

    .get-my-thing(@my-bool);

}

.get-my-thing(@switch) when (@switch) {
     width: 100%;
}

Is there a reason something like the above hasn't been implemented?

@seven-phases-max
Copy link
Member

Is there a reason something like the above hasn't been implemented?

Well, it is not that easy as you might think. Notice that most of comparison operators are the same symbols as CSS selector combinators so such implementation needs very detailed specification of where they mean either of two (e.g. notice that 2 > 1 is also a valid Less selector and a > b is also a valid comparison (not counting that both are useless)). It's not that this is impossible to implement at all (e.g. + operator already works as a selector combinator and as an arithmetic operator) but it's pretty much head-scratching to "design". (For example, recently we were considering if it's possible to allow to assign selectors to variables (since it already works unofficially) and then it would be direct conflict in @var: .2 > .1; statement. And though most likely it won't go that "selector" way because it's too ambiguous, the same "ambiguous story" applies to interpreting @var: .2 > .1; as a comparison).

@skyshab
Copy link
Author

skyshab commented Feb 25, 2014

@seven-phases-max

I needed slightly more functionality, but your example helped a lot.

Probably superfluous in most cases, but those who come here wanting to use conditionals might find it useful.

https://gist.github.com/skyshab/9217113

@calvinjuarez
Copy link
Member

Updated link: https://gist.github.com/skyshab/9217113

@matthew-dean
Copy link
Member

Closing in favor of: #3019

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

5 participants