0

Bootstrap uses some LESS mixins to generate it's column classes (and several other classes);

.make-grid-columns() {
  // Common styles for all sizes of grid columns, widths 1-12
  .col(@index) when (@index = 1) { // initial
    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
    .col((@index + 1), @item);
  }
  .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
    .col((@index + 1), ~"@{list}, @{item}");
  }
  .col(@index, @list) when (@index > @grid-columns) { // terminal
    @{list} {
      position: relative;
      // Prevent columns from collapsing when empty
      min-height: 1px;
      // Inner gutter via padding
      padding-left:  (@grid-gutter-width / 2);
      padding-right: (@grid-gutter-width / 2);
    }
  }
  .col(1); // kickstart it
}

I can see that LESS mixin guards are being used to create loops, and I can understand the code examples that are given in the LESS documentation;

.loop(@counter) when (@counter > 0) {
  .loop((@counter - 1));    // next iteration
  width: (10px * @counter); // code for each iteration
}

div {
  .loop(5); // launch the loop
}

But I can't seem to grok exactly how the more complex nested guard expressions that bootstrap uses are working. Could somebody comment the above bootstrap code in a bit more detail to give me an indication of what is going on?

axdg
  • 4,025
  • 3
  • 13
  • 7
  • This loop is basicaly the same as the same as the second example in [the documentation](http://lesscss.org/features/#loops-feature) (when it comes to recursion and guards). The only difference is the additional "terminal" mixin which is used to actually render the selector list string generated by previous iterations. So what are the exact statements being problematic for you? (Considering that the only used guard expressions are simple `=`, `<=`, `>`). – seven-phases-max Aug 14 '14 at 11:15
  • Apologies, I'm new to LESS and have just started running through the documentation (which I see that you have contributed to). I've just been through the pattern matching section - It was the nested .col selectors (eg .col((@index + 1), @item); ) that were confusing me, but I seem to have gotten it now. – axdg Aug 14 '14 at 13:27
  • That second example is also excellent - cheers. – axdg Aug 14 '14 at 13:31
  • Just in case, please do not consider this Bootstrap grid code as something to learn "modern" Less with. In fact this piece of code (in particular its "generating selector list via string concatenation") is very ancient snippet and it really shows "oldish" way of doing things :). (Curiously once we had a nice [discussion](https://github.com/less/less.js/issues/1785#issuecomment-31884999) about it). – seven-phases-max Aug 14 '14 at 13:48
  • It's still a pity that [0-parameter mixins can't be extended](https://github.com/less/less.js/issues/1177) though. – cvrebert Aug 14 '14 at 22:55
  • @cvrebert I'm planning to write a small blog post on refactoring Boostrap grid implementation. In fact we don't even #1177 need to do this (see my [last message](https://github.com/less/less.js/issues/1785#issuecomment-33120833) at #1785, though I still think that extending "dummy classes" method would be the most "future-safe" this time (for various minor issues/reasons)). – seven-phases-max Aug 16 '14 at 00:57
  • 1
    [Done](https://github.com/seven-phases-max/less.curious/blob/master/articles/rbgi.md). – seven-phases-max Aug 17 '14 at 18:54

1 Answers1

1

The purpose of the .make-grid-columns() mixin is to generate a long list of selectors which all share the same properties. This list can not be hard code in the code because of the number of columns (@grid-columns) may vary.

You already illustrated the basics of a loop in Less in the question yourself.

To understand the mixins you will have to understand Less allows you the use the same mixin name many times. Every 'matching' mixins will be compiled into the CSS code. Mixin guards when () enable you to set a condition for the match. When the guard not match the mixin is not compiled. Beside mixins guards you can also use pattern matching, than you can match on value as follows:

.mixin1(a,@width){}
.mixin1(b,@width){}

The .mixin(a,20px); call match only the first mixin. Partern matching based on arity (the number of arguments) will also works. Notice that .col(@index) when (@index = 1) does not need a guard (see also). the .col(@index) call has only one argument, so the .col(1); only matches that mixin based on arity matching. The .col(@index) mixin calls the .col(@index, @list) mixin(s). The .col(@index) when (@index = 1) mixin will be only called for the first iteration. The reason for having two mixins in stead of one is that Less don't support if / else. The list of selectors can not start or end with a comma and so the first or last item in the list of selectors should differ from the others.

Alternatively you can use a mixin with an additional argument:

.mixin(@iterator; @item:~""; @seperator:~"") when (@iterator < 5){
@list: ~"@{item} @{seperator} @{iterator}";
.mixin((@iterator + 1); @list; ",");
}
.mixin(@iterator; @list; @seperator) when (@iterator = 5){
.selector{
@{list}: value;
}
}
.mixin(1);

The @seperator will be empty (~"") for the first call and a comma (",") for all other calls. Notice that a mixin with default parameters also match calls which not having set values for the defaults: So .call(1); matches the .call(@a; @b:4; @c:5){} mixin.

As already mentioned in the comments ~"@{list}, @{item}" generates the selector list via string concatenation.

The last iteration of .col(@index, @list) when (@index =< @grid-columns) calls col((@grid-columns + 1)....) mixin when @index=@grid-columns and so matches the last .col(@index, @list) when (@index > @grid-columns) mixin in the structure.

@{list} { } use selector interpolation to set the list of selectors and his properties.

Of course you should also read the excellent blog post of @seven-phases-max about this structure to generate a list of selectors.

Finally you should know that Bootstrap needs such a long list of selectors because of it avoid (partial) attribute selectors. In stead of the selector list you can also use the following CSS / Less code:

[class^="col-"], [class*=" col-"]
{
      position: relative;
      // Prevent columns from collapsing when empty
      min-height: 1px;
      // Inner gutter via padding
      padding-left:  (@grid-gutter-width / 2);
      padding-right: (@grid-gutter-width / 2);
}

The reason to avoid attribute selectors is that some browsers compute them slow. As can be seen at http://benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/, you can discuse this argument. Personally i think that unused code is a more important performance issue than the use of attribute selectors in most Bootstrap projects.

Bass Jobsen
  • 48,736
  • 16
  • 143
  • 224