2

I'm making a view that displays transactions on an account, and I want to colour-code the transaction type/state. I also want to show a legend explaining the colour codes.

I want an end result that's structured like this:

HTML

<table id="transactions">
  <thead>
    <tr>
      <th colspan="2">
        Transactions
      </th>
    </tr>
  </thead>
  <tbody>
    <tr class="credit">
      <td>A credit</td>
      <td>$1.00</td>
    </tr>
    <tr class="debit paid">
      <td>A paid debit</td>
      <td>($2.00)</td>
    </tr>
    <tr class="debit unpaid">
      <td>An unpaid debit</td>
      <td>($3.00)</td>
    </tr>
  </tbody>
</table>
<hr/>
<table id="legend">
  <thead>
    <tr>
      <th colspan="3">
        Legend
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="credit">Credit</td>
      <td class="debit paid">Paid</td>
      <td class="debit unpaid">Unpaid</td>
    </tr>
  </tbody>
</table>

CSS

table#transactions > tbody > tr.credit {
  color: limegreen;
}

table#legend > tbody > tr > td.credit {
  color: limegreen;
}

table#transactions > tbody > tr.debit.paid {
  color: goldenrod;
}

table#legend > tbody > tr > td.debit.paid {
  color: goldenrod;
}

table#transactions > tbody > tr.debit.unpaid {
  color: crimson;
}

table#legend > tbody > tr > td.debit.unpaid {
  color: crimson;
}

(CodePen)

Note that the "debits" use two class names, to tell them apart from the credits.

Clearly there's a bit of redundancy there, which I tried to refactor into this (invalid) LESS code:

.transaction-status(@class, @color) {
  table#transactions > tbody > tr@{class} {
    color: @color;
  }

  table#legend > tbody > tr > td@{class} {
    color: @color;
  }  
}

.transaction-status(.credit, limegreen);
.transaction-status(.debit.paid, goldenrod);
.transaction-status(.debit.unpaid, crimson);

A possible workaround would be to rejigger things so the different transaction types have a single unique class name but that feels like time travelling to the time of IE6. I.e. I'm aware of, but would like to avoid this valid LESS, which seems so close, and yet so far:

.transaction-status(@class, @color) {
  table#transactions > tbody > tr.@{class} {
    color: @color;
  }

  table#legend > tbody > tr > td.@{class} {
    color: @color;
  }  
}

.transaction-status(credit, limegreen);
.transaction-status(debit-paid, goldenrod);
.transaction-status(debit-unpaid, crimson);

I tried quoting the class names, but even though that makes the first LESS sample compile, the quotes are passed to the output CSS. So, is there a way to pass something else than an "identifier" as a parameter to a LESS mixin, and have it work in selector interpolation correctly?

Harry
  • 87,580
  • 25
  • 202
  • 214
millimoose
  • 39,073
  • 9
  • 82
  • 134
  • Why can't you use the one where the `.` is in the selector instead of the argument? – jacksondc Sep 30 '14 at 02:19
  • 2
    Just incase for this particular case it's also possible to use [`appended parent selector`](http://codepen.io/seven-phases-max/pen/vjeGr?editors=110) trick though this method has its cons and not always smells good. – seven-phases-max Sep 30 '14 at 02:37
  • 1
    And btw. I always wonder why everybody prefer `table#legend > tbody > tr > td.debit.unpaid` to just `table .debit.unpaid` (You dont't have other tables with those cells colored the other way, do you?). – seven-phases-max Sep 30 '14 at 02:39
  • @seven-phases-max: I actually think that the appended parent selector method is also very good. – Harry Sep 30 '14 at 02:43
  • @seven-phases-max Probably just me wearing blinkers. I have some other reusable (OOCSS-ish) table styles that need to be that precise, so the approach was burned in my brain. Thanks for both your suggestions, I wasn't aware of `&` working that way. – millimoose Sep 30 '14 at 20:25

2 Answers2

2

Option 1:

As you mentioned, one option is to pass the value within quotes. But when that is done, you need to make sure to remove them before using them to perform selector interpolation. This can be done by either using the ~() or the e() built-in functions. Both of them will strip out the quotes from the input value. Once this is done, the temporary variable whose value doesnt have the quotes can be used for selector interpolation like below:

.transaction-status(@class, @color) {
    @className: ~"@{class}";
    table#transactions > tbody > tr@{className} {
        color: @color;
    }

    table#legend > tbody > tr > td@{className} {
        color: @color;
    }  
}

.transaction-status(".credit", limegreen);
.transaction-status(".debit.paid", goldenrod);
.transaction-status(".debit.unpaid", crimson);

Option 2: (a bit round about in my opinion)

You can also use the ... option to pass in as many classes as required (note that this needs a small change to the order in which the inputs are passed) and then convert it to a string and use the replace function to add the .. The replace function is required because the converted string would be of the format class1 class2 (space separator).

.transaction-status(@color, @class...) {
    @className: e(replace("@{class}" , " " , ".", 'g' )); 
    /* arguments: input string, pattern to match, replacement value, regex flags */
    table#transactions > tbody > tr.@{className} {
        color: @color;
    }

    table#legend > tbody > tr > td.@{className} {
        color: @color;
    }   
}

.transaction-status(limegreen, credit);
.transaction-status(goldenrod, debit, paid);
.transaction-status(crimson, debit, unpaid);

Note: Option 2 would work only with Less v.1.7.0 and above because the replace function was introduced only in v1.7.0.

Harry
  • 87,580
  • 25
  • 202
  • 214
  • 1
    I think I was fishing for the `~` approach, I tried using it but kept receiving errors, and thought the syntax might have been deprecated. Thanks. – millimoose Sep 30 '14 at 20:26
2

It looks like you could color the whole row in either case? If so, a simple nesting of your selectors would reduce duplication, and arguably be easier to read:

table#transactions, table#legend {
    & > tbody > tr {
      &.credit {
        color: limegreen;
      }

      &.debit.paid {
        color: goldenrod;
      }

      &.debit.unpaid {
        color: crimson;
      }
    }
  }
}

But that doesn't let you easily reuse these class/color combinations in other parts of your code, which is something that you very well may want to do. I would define your color classes more like this:

.trans-status {
  &.credit {
    color: limegreen;
  }

  &.debit.paid {
    color: goldenrod;
  }

  &.debit.unpaid {
    color: crimson;
  }
}

And then you can apply the appropriate classes with more precision:

<tr>
  <td>An unpaid debit</td>
  <td class="trans-status debit unpaid">($3.00)</td>
</tr>

Here's a fork of your example using this approach:

https://codepen.io/anon/pen/mxMLPG

Another advantage to using this approach, is that you can also apply these styles as mixins in your Less files. For example, if you needed to use the same color values in other parts of your code, you could define the colors more abstractly, and apply them in different ways:

.status {
  .success {
    color: limegreen;
  }

  .confirmed {
    color: goldenrod;
  }

  .warning {
    color: crimson;
  }
}

.negative-balance {
  .status.warning;
}

#rewards-points {
  .status.success;
}

It's also worth noting that in cases where you do need to pass rules/mixins into other mixins, Less 1.7.0 and above supports Detatched Rulesets to accomplish this same sort of thing with a cleaner syntax than string interpolation. The original question doesn't really warrant this level of complexity, but it can be useful for things like breakpoints:

.break(@min-width; @max-width; @rules) {
  @media only screen and (min-width:@min-width) 
    and (max-width: @max-width){
    @rules();
  }
}

.break(0px, 480px, {
  font-size: 10px;
  padding: 8px;
});
Chris Jaynes
  • 2,868
  • 30
  • 29