├── 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 |
--------------------------------------------------------------------------------