├── src ├── for.less └── for-each.less └── articles ├── generic-for.md ├── for-each.md └── rbgi.md /src/for.less: -------------------------------------------------------------------------------- 1 | 2 | // Deprecated. Use https://github.com/seven-phases-max/less-plugin-lists instead 3 | 4 | // ............................................................ 5 | // .for 6 | 7 | .for(@i, @n) {.-each(@i)} 8 | .for(@n) when (isnumber(@n)) {.for(1, @n)} 9 | .for(@i, @n) when not (@i = @n) { 10 | .for((@i + (@n - @i) / abs(@n - @i)), @n); 11 | } 12 | 13 | // ............................................................ 14 | // .for-each 15 | 16 | .for(@array) when (default()) {.for-impl_(length(@array))} 17 | .for-impl_(@i) when (@i > 1) {.for-impl_((@i - 1))} 18 | .for-impl_(@i) when (@i > 0) {.-each(extract(@array, @i))} 19 | -------------------------------------------------------------------------------- /src/for-each.less: -------------------------------------------------------------------------------- 1 | 2 | // Deprecated. Use https://github.com/seven-phases-max/less-plugin-lists instead 3 | 4 | // ............................................................ 5 | // this is list/array only for-each version 6 | // useful in situation where the list may have the only numeric item 7 | // (in that case the original implementation will result in: 8 | // .for(4px) -> loop through values from 1px to 4px) 9 | 10 | // ............................................................ 11 | 12 | .for(@array) when (default()) {.for-impl_(length(@array))} 13 | .for-impl_(@i) when (@i > 1) {.for-impl_((@i - 1))} 14 | .for-impl_(@i) when (@i > 0) {.-each(extract(@array, @i))} 15 | -------------------------------------------------------------------------------- /articles/generic-for.md: -------------------------------------------------------------------------------- 1 | ### Deprecated. Use [`less-plugin-lists`](https://github.com/seven-phases-max/less-plugin-lists#features) instead. 2 | 3 | --- 4 | 5 | ## Generic `for` Structure in [Less](http://lesscss.org/) Using Mixins 6 | 7 | ### Basic usage 8 | Less code: 9 |
@import "for";
 10 | 
 11 | #basic-usage {
 12 |     .for(6); .-each(@i) {
 13 |         i: @i;
 14 |     }
 15 | }
 16 | 
17 | 18 | CSS output: 19 | ```css 20 | basic-usage { 21 | i: 1; 22 | i: 2; 23 | i: 3; 24 | i: 4; 25 | i: 5; 26 | i: 6; 27 | } 28 | ``` 29 | ### More real-world example 30 | Less code: 31 | ```less 32 | @grid-columns: 5; 33 | 34 | .column { 35 | .for(@grid-columns); .-each(@i) { 36 | &-@{i} {width: (@i / @grid-columns * 100%)} 37 | } 38 | } 39 | ``` 40 | CSS output: 41 | ```css 42 | .column-1 { 43 | width: 20%; 44 | } 45 | .column-2 { 46 | width: 40%; 47 | } 48 | .column-3 { 49 | width: 60%; 50 | } 51 | .column-4 { 52 | width: 80%; 53 | } 54 | .column-5 { 55 | width: 100%; 56 | } 57 | ``` 58 | More examples 59 | --------------------- 60 | 61 | ### Nested loops: 62 | ```less 63 | #nested-loops { 64 | .for(3, 1); .-each(@i) { 65 | .for(0, 2); .-each(@j) { 66 | x: (10 * @i + @j); 67 | } 68 | } 69 | } 70 | ``` 71 | output: 72 | ```css 73 | #nested-loops { 74 | x: 30; 75 | x: 31; 76 | x: 32; 77 | x: 20; 78 | x: 21; 79 | x: 22; 80 | x: 10; 81 | x: 11; 82 | x: 12; 83 | } 84 | ``` 85 | ### Multiple loops in same scope: 86 | ```less 87 | #multiple-loops { 88 | & {.for(1, 3); .-each(@i) {x: @i}} 89 | & {.for(4, 6); .-each(@i) {y: @i}} 90 | } 91 | ``` 92 | output (Less 1.6.2 and higher): 93 | ```css 94 | #multiple-loops { 95 | x: 1; 96 | x: 2; 97 | x: 3; 98 | y: 4; 99 | y: 5; 100 | y: 6; 101 | } 102 | ``` 103 | ### Multiple loops in same scope (Alt.): 104 | ```less 105 | #multiple-loops-alt { 106 | .-() {.for(1, 3); .-each(@i) {x: @i}} 107 | .-() {.for(4, 6); .-each(@i) {y: @i}} 108 | .-(); 109 | } 110 | ``` 111 | output: 112 | ```css 113 | #multiple-loops-alt { 114 | x: 1; 115 | x: 2; 116 | x: 3; 117 | y: 4; 118 | y: 5; 119 | y: 6; 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /articles/for-each.md: -------------------------------------------------------------------------------- 1 | ### Deprecated. Use [`less-plugin-lists`](https://github.com/seven-phases-max/less-plugin-lists#features) instead. 2 | 3 | --- 4 | 5 | ## Generic `for-each` Structure in [Less](http://lesscss.org/) Using Mixins 6 | 7 | ### Basic Usage 8 | Less code: 9 |
@import "for";
 10 | 
 11 | @list: banana, apple, pear, potato, carrot, peach;
 12 | 
 13 | #basic-usage {
 14 |     .for(@list); .-each(@value) {
 15 |         value: @value;
 16 |     }
 17 | }
 18 | 
19 | 20 | CSS output: 21 | ```css 22 | basic-usage { 23 | value: banana; 24 | value: apple; 25 | value: pear; 26 | value: potato; 27 | value: carrot; 28 | value: peach; 29 | } 30 | ``` 31 | 32 | ### Practical Examples 33 | Less code: 34 | ```less 35 | 36 | .transition(@properties, @value...) { 37 | .for(@properties); .-each(@property) { 38 | transition+: @property @value; 39 | } 40 | } 41 | 42 | div { 43 | @properties: color, background-color, border-color; 44 | .transition(@properties, 2s, ease-out); 45 | } 46 | 47 | .button { 48 | .transition(background-color border-color, 1s ease-in); 49 | } 50 | 51 | .another { 52 | .transition(all, 4s); 53 | } 54 | ``` 55 | CSS output: 56 | ```css 57 | div { 58 | transition: color 2s ease-out, background-color 2s ease-out, border-color 2s ease-out; 59 | } 60 | .button { 61 | transition: background-color 1s ease-in, border-color 1s ease-in; 62 | } 63 | .another { 64 | transition: all 4s; 65 | } 66 | ``` 67 | Less code: 68 | ```less 69 | 70 | #icon { 71 | .for(home ok cancel error book); .-each(@name) { 72 | &-@{name} { 73 | background-image: url("../images/@{name}.png"); 74 | } 75 | } 76 | } 77 | ``` 78 | CSS output: 79 | ```css 80 | #icon-home { 81 | background-image: url("../images/home.png"); 82 | } 83 | #icon-ok { 84 | background-image: url("../images/ok.png"); 85 | } 86 | #icon-cancel { 87 | background-image: url("../images/cancel.png"); 88 | } 89 | #icon-error { 90 | background-image: url("../images/error.png"); 91 | } 92 | #icon-book { 93 | background-image: url("../images/book.png"); 94 | } 95 | ``` 96 | 97 | More examples 98 | --------------------- 99 | ### Nested loops: 100 | ```less 101 | #nested-loops { 102 | .for(a b c); .-each(@a) { 103 | .for(1 2 3); .-each(@b) { 104 | x: @a @b; 105 | } 106 | } 107 | } 108 | ``` 109 | output: 110 | ```css 111 | #nested-loops { 112 | x: a 1; 113 | x: a 2; 114 | x: a 3; 115 | x: b 1; 116 | x: b 2; 117 | x: b 3; 118 | x: c 1; 119 | x: c 2; 120 | x: c 3; 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /articles/rbgi.md: -------------------------------------------------------------------------------- 1 | 2 | **Note (2017-04-29)**: This article was only about cleaning up the particular Bootstrap 3 code. In general, this is **not** how I recommend to implement these things if you're writing your grid stuff from scratch (see https://github.com/less/less.js/issues/2702#issuecomment-144810471 for a few examples). 3 | 4 | --- 5 | 6 | ## On refactoring Boostrap 3 grid implementation. 7 | 8 | ### See [Inspiring discussion](https://github.com/less/less.js/issues/1785#issuecomment-31876655) first. 9 | 10 | So indeed, every time I write some `.col-md-6` in my HTML I recall how this selector was generated and this actually drives me crazy :) 11 | 12 | Here's that "horrible" [`.make-grid-columns`](https://github.com/twbs/bootstrap/blob/v3.2.0/less/mixins/grid-framework.less#L6-L27) code (same goes for [`.float-grid-columns`](https://github.com/twbs/bootstrap/blob/v3.2.0/less/mixins/grid-framework.less#L29-L44)): 13 | ```less 14 | .make-grid-columns() { 15 | // Common styles for all sizes of grid columns, widths 1-12 16 | .col(@index) when (@index = 1) { // initial 17 | @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; 18 | .col((@index + 1), @item); 19 | } 20 | .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo 21 | @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; 22 | .col((@index + 1), ~"@{list}, @{item}"); 23 | } 24 | .col(@index, @list) when (@index > @grid-columns) { // terminal 25 | @{list} { 26 | position: relative; 27 | // Prevent columns from collapsing when empty 28 | min-height: 1px; 29 | // Inner gutter via padding 30 | padding-left: (@grid-gutter-width / 2); 31 | padding-right: (@grid-gutter-width / 2); 32 | } 33 | } 34 | .col(1); // kickstart it 35 | } 36 | ``` 37 | Doh! 38 | 39 | No, actually, strictly speaking, there's nothing wrong with this code. It's valid Less, it does what it is supposed to do and works like a charm after all. It's just "not elegant". And this "not elegant" is actually enough reason for me (who spent a lot of time and efforts on advertising and pushing "a modern and proper methods of working with Less lists/arrays/loops and related stuff") to try to find a better way to generate equal CSS. 40 | 41 | (Well, even more strictly speaking, there *are* other more rational reasons for such "string-based selector manipulation" to be avoided whenever possible, but that's another big story so here I'll stop at just "not elegant"). 42 | 43 | - 44 | ### So the problem: 45 | > There's no straight-forward method to generate and use a list of selectors in Less. 46 | 47 | There are some feature requests/ideas that will improve the situation eventually (see [#1421](https://github.com/less/less.js/issues/1421), [#1177](https://github.com/less/less.js/issues/1177) etc.), but they at best are in their early planning stage so it's hard to say when we'll finally be able to rewrite this code "the right way". 48 | 49 | ### So the task: 50 | > Can we rewrite Bootstrap grid generation code in not so hackish way *right now*? 51 | 52 | Currently, the only "Less-native"/"non-hackish" way to generate a ruleset with a list of arbitrary selectors is the [`extend`](http://lesscss.org/features/#extend-feature) feature. So the first (and the simplest) solution is to move the styles of the generated selector list into a ruleset with some predefined name and then just `extend` it by generated classes: 53 | 54 | ### Method #1 ("Dummy Classes"). 55 | ```less 56 | .grid-column-any { 57 | // Common styles for all sizes of grid columns 58 | position: relative; 59 | min-height: 1px; 60 | padding-left: (@grid-gutter-width / 2); 61 | padding-right: (@grid-gutter-width / 2); 62 | } 63 | 64 | .make-grid-columns() { 65 | .col(@index) when (@index > 0) { 66 | .col((@index - 1)); 67 | .col-xs-@{index}, 68 | .col-sm-@{index}, 69 | .col-md-@{index}, 70 | .col-lg-@{index} { 71 | &:extend(.grid-column-any); 72 | } 73 | } 74 | .col(@grid-columns); 75 | } 76 | ``` 77 | That's it (actual code for complete `grid-framework.less` will be a bit different to accommodate various scoping and `@media` stuff but the principle remains the same: see [the branch](https://github.com/seven-phases-max/bootstrap/blob/refactoring-grid-framework-m1/less/mixins/grid-framework.less)). The only problem of this approach is that the compiled CSS now contains that "unused" "private" `.grid-column-any` selector and complete `grid-framework.less` would need 4 or 5 of those (Below I will refer to such selectors as just "dummy" classes/selectors/templates). 78 | 79 | - 80 | > Getting rid of the dummy selectors... 81 | 82 | If Less has the [":extend mixins" feature](https://github.com/less/less.js/issues/1177) we could simply turn the template into a mixin and it won't appear in the CSS, but Less has not... so it's time for some trick. 83 | ### Method #2 ("Extending `.col-*-1` classes"). 84 | And here we are, set the template styles in the first column classes and than `extend` this first column(s) by subsequent column classes: 85 | ```less 86 | .col-xs-1, 87 | .col-sm-1, 88 | .col-md-1, 89 | .col-lg-1 { 90 | // Common styles for all sizes of grid columns 91 | position: relative; 92 | min-height: 1px; 93 | padding-left: (@grid-gutter-width / 2); 94 | padding-right: (@grid-gutter-width / 2); 95 | } 96 | 97 | .make-grid-columns() { 98 | .col(@index) when (@index > 1) { 99 | .col((@index - 1)); 100 | .col-xs-@{index}, 101 | .col-sm-@{index}, 102 | .col-md-@{index}, 103 | .col-lg-@{index} { 104 | &:extend(.col-xs-1); 105 | } 106 | } 107 | .col(@grid-columns); 108 | } 109 | ``` 110 | (Yet again the actual code of `grid-framework.less` would be slightly different: see [the branch](https://github.com/seven-phases-max/bootstrap/blob/refactoring-grid-framework-m2/less/mixins/grid-framework.less).) 111 | 112 | Unfortunately this approach has one quite critical problem. While such mixin works just fine on its own, in the framework it may interfere with and be broken by another grid related code. Basically if you for example add another `.col-xs-1` definition/styles anywhere at the global scope of your Less code, e.g.: 113 | ```less 114 | .col-xs-1 { 115 | color: red; 116 | } 117 | ``` 118 | the styles of this new definition will automatically propagate to *every other* grid column. Fortunately, currently even if other Bootstrap grid mixins *do* generate other first column styles these other styles *do not* break anything simply because their selectors are generated via selector interpolation and `extend` does not work with such dinamically generated selectors. 119 | 120 | So this method may still be considered as a working temporary solution, but only if we don't want the Method #1 with its dummy classes really badly deadly. After all this "extending first column" method will be irrecoverably broken as soon as Less brings "extending dinamicaly generated selectors" feature. And before that happens the mentioned potential issues will keep hiding there with its `.col-xs-1 {color: red}` grenade ready. 121 | 122 | - 123 | > Getting rid of the dummy selectors, the other way around... 124 | 125 | ### Method #3 ("Emulating #1177"). 126 | It is possible to emulate the [":extend mixins" feature](https://github.com/less/less.js/issues/1177) by `extend`ing a ruleset defined in a file imported with [`reference`](http://lesscss.org/features/#import-options-reference) option. I.e. we can move our dummy templates to a separate file, `@import (reference) "it";` and voilà, no dummy selectors in the generated CSS: 127 | 128 | ```less 129 | // ............................................................ 130 | // grid-aux.less: 131 | 132 | .grid-column-any { 133 | // Common styles for all sizes of grid columns 134 | position: relative; 135 | min-height: 1px; 136 | padding-left: (@grid-gutter-width / 2); 137 | padding-right: (@grid-gutter-width / 2); 138 | } 139 | 140 | // ............................................................ 141 | // grid-framework.less: 142 | 143 | @import (reference) "grid-aux.less"; 144 | 145 | .make-grid-columns() { 146 | .col(@index) when (@index > 0) { 147 | .col((@index - 1)); 148 | .col-xs-@{index}, 149 | .col-sm-@{index}, 150 | .col-md-@{index}, 151 | .col-lg-@{index} { 152 | &:extend(.grid-column-any); 153 | } 154 | } 155 | .col(@grid-columns); 156 | } 157 | ``` 158 | Clean and relatively simple (not counting it brings additional file(s) and requires use of `reference` never used in Bootstrap code before). 159 | 160 | Unfortunately the things become not so simple (and even less clean) if we try to apply this approach to other `grid-framework` mixins (the one that generates media depended column rulesets in particular). To be able to `extend` a template ruleset within a `@media` block we need such ruleset to be defined *in* this `@media` block (see ["Scoping / Extend Inside @media"](http://lesscss.org/features/#extend-feature-scoping-extend-inside-media)). It's definitely possible (for example we can import our auxiliary file right within a column generating mixin so it "automatically" brings the necessary templates into corresponding `@media` [blocks](https://github.com/twbs/bootstrap/blob/v3.2.0/less/grid.less#L56) (I actually have a working concept somewhere among my local branches)), but the complete `grid-framework` implementation using this approach becomes so tricky, cryptic and unreadable so it actually spoils the very idea of the refactoring (i.e. we'll just replace one "non-elegant and hackish" implementation with another "maybe a bit more elegant but even yet more hackish" one). 161 | 162 | So I would not consider going this "reference" route seriously (for the Bootstrap code-base at least). 163 | 164 | - 165 | ### Summary: 166 | 167 | **Method #1. "Extending dummy templates":** 168 | 169 | - *Cons*: Dummy classes in the compiled CSS. 170 | - *Pros*: The most future-proof and maintainable. 171 | 172 | My vote definitely goes for this method. After all who does care of four or five unused identifiers in a CSS with (several?) thousands of those? :) 173 | 174 | **Method #2. "Using `col-*-1` rulesets as template styles":** 175 | 176 | - *Pros*: The most "optimal" for the moment (no dummy classes in the output). 177 | - *Cons*: Quite flawed. Busts when any additional styles are defined with explicitly specified "col-*-1" selectors (thus it will require extra efforts for tracking if any further changes in the code base do not actually break the grid somehow). It also gets finally broken when Less brings "extending dynamically generated selectors" feature. 178 | 179 | **Method #3. "Extending dummy templates with `reference`":** 180 | 181 | - *Pros*: No dummy classes in the output. More safe than **#2**. 182 | - *Cons*: Too tricky and too bloating code. 183 | 184 | --- 185 | 186 | ### Also see: 187 | * [this ticket](https://github.com/less/less.js/issues/2702#issuecomment-142942530) for "What's wrong with Bootstrap-like media/grid code in general". 188 | --------------------------------------------------------------------------------