2

I have a problem with sprites background position calculation using a variable:

My code looks something like this:

@counter: 1;

#my-icon-bundle {
.my-icons () {
  #my-icon-bundle .myIconX("classX1", @counter);
  #my-icon-bundle .myIconX("classYY1", @counter);
  ...
}

.myIconX(@name, @index) {
  @nameText: ~".my-icon-@{name}";
  @{nameText} { #my-icon-bundle .myIcon(@index); }

  @counter: @index + 1;
}

.myIcon(@row) {
  @x: some calculations based on @row
  @y: some calculations based on @row
  background-position: -@x -@y;
}
}

The problem is that the @counter increment does not work properly, and all icons appear as the second icon in the sprite image, if we replace:

#my-icon-bundle .myIconX("classX1", @counter);

with the value of the counter it appears correctly...any ideas how to increment the global value properly? Thanks (PS: I'm using less 1.4.2)

7
  • Could you elucidate where you need to use the counter? (Its value is not used in your example so it's hard to suggest something definite. Though I suspect that what you want could be achieved with LESS list->loop.) Commented Nov 25, 2013 at 18:03
  • @seven-phases-max: I pondered if a loop would fix it as well, but it appears that the desire is for an arbitrary namespaced call to include an icon like this #my-icon-bundle .myIconX("classX1", @counter); to also increment the counter. If so, then a loop is not going to help, because all the icons are not necessarily intended to be defined, but rather only those that are explicitly called. Commented Nov 25, 2013 at 18:13
  • @ScottS, Yes, this could be an issue. Though, if I understand it right, a sprite has a predefined number of icons so each name like "classX1" should correspond to a specific icon index (= counter?). And we still can generate this index by searching through a list like @names: "classX1", "classYY1", "etc.". Something like `getIconIndex(@name)' mixin/function, could be too verbose though. Commented Nov 25, 2013 at 18:40
  • @seven-phases-max: I agree that it may be a solution. It appears as though the idea is not to clutter the code with every sprite index (which is what would happen if generated for all). BTW - your list and loop solution would work if such full code generation was desired, but I was going to point out in a comment (you deleted it before I could finish) that your list needed to be only space separated, not comma separated, since comma separation for extract() was not introduced until LESS 1.5. Commented Nov 25, 2013 at 18:45
  • @ScottS, thanks. I think I'll keep my answer deleted since it could be misleading. I hope we'll be given some more details on possible .myIconX uses. Commented Nov 25, 2013 at 19:01

2 Answers 2

3

Strictly Speaking You Cannot

Variables in LESS are essentially constants once defined in a particular scope, and so cannot be changed (including incremented). So your @counter: @index + 1; is not incrementing the global variable at all, but rather creating a new value for a local scope @counter variable inside that particular call of .myIconX(). See the documentation on how variables work in LESS.

Emulated by Recursive Local Variable Setting

This works, based off information deemed a bug here, but which I do not believe is strictly speaking a bug. At any rate, it can be utilized to meet your needs like so (I just implemented an @row: 1 and tweaked some code to show the calculation working):

@row: 1;

.init() {
.inc-impl(1);
} .init();

.inc-impl(@new) {
.redefine() {
@counter: @new;
}
}

#my-icon-bundle {
.my-icons () {
  #my-icon-bundle .myIconX("classX1", @counter);
  #my-icon-bundle .myIconX("classYY1", @counter);
}

.myIconX(@name) {
   .redefine();
  .inc-impl((@counter + 1));
  @nameText: ~".my-icon-@{name}";
  @{nameText} { #my-icon-bundle .myIcon(@row); }
}

.myIcon(@row) {
  @x: @row * @counter;
  @y: @row * @counter;
  background-position: -@x -@y;
}
}

#my-icon-bundle .myIconX("classX1");
#my-icon-bundle .myIconX("classX1");
#my-icon-bundle .myIconX("classYY1");

Output CSS is:

.my-icon-classX1 {
  background-position: -1 -1;
}
.my-icon-classX1 {
  background-position: -2 -2;
}
.my-icon-classYY1 {
  background-position: -3 -3;
}

This demonstrates that with each call of the .myIconX() mixin, it is setting the counter by +1 for the next call.

Warning: Whether this solution is based on buggy behavior or not is questionable, but if it is a bug, this solution may disappear in the future. For further comments on the limitations of this method, see the discussion here.

Sign up to request clarification or add additional context in comments.

3 Comments

After some thinking I came to conclusion that the counter way is still not a best solution for this particular use case. The counter has the same problem you've mentioned before: "because all the icons are not necessarily intended to be defined, but rather only those that are explicitly called". Suppose you have a sprite with 9 icons - now to get to icon-9 you still have to call all 8 .myIconXs first (so that all 9 selectors go to output even if you need only one).
@seven-phases-max: that depends on what they are keeping "count" for. They do not say. If for some reason they are simply counting the number of icons defined, that would be why they would want an independent counter. The "use" of the count was not given in the question.
@ScottS. You're right, a connection between those "classX1" strings and their counter value is still not evident and it dramatically affect everything. Though Alex mentioned that "row is actually index" it's still a bit unclear ("row" of what? A row of the sprite or a row of the .my-icon-class container?)
1

Since a counter based solution seems to still may have some shortcomings depending on possible use-cases (beside the hack-based counter thing itself, see my comment to the corresponding answer) I decided to post a list/loop-based solution I mentioned earlier. I keep the code here as close as possible to the counter-based one so they could be easily compared. (But in general all could be made much clean, structured and generic with further polishing by renaming and reordering all those namespaces/selectors/mixins/variables, removing unnecessary quotes etc.).

Opt. 1

When you need only arbitrary icon(s) of the sprite to have its class in the CSS output:

@row: 1;

// ......

.my-icon-bundle {
    .myIcon(@row, @index) {
        @x: (@row * @index);
        @y: (@row * @index);
        background-position: -@x -@y;
    }

    .myIconX(@name) {
        @icons:
            "classX1",
            "classYY1",
            "classZZZ",
            "anotheRR9",
            "etc.";

        .find(1);
        .find(@i) when (@name = extract(@icons, @i)) {
            @name_: e(@name);
            .my-icon-@{name_} {
                #my-icon-bundle.myIcon(@row, @i);
            }
        }
        .find(@i) when not
            (@name = extract(@icons, @i)) {
                .find((@i + 1));
        }
    }
}

// ......
// usage:

#my-icon-bundle.myIconX("anotheRR9");
#my-icon-bundle.myIconX("classX1");

Opt. 2

When you just need to generate corresponding classes for all icons in the sprite:

@row: 1;

// ......

#my-icon-bundle {
    .myIcon(@row, @index) {
        @x: (@row * @index);
        @y: (@row * @index);
        background-position: -@x -@y;
    }

    .icons() {
        @icons:
            "classX1",
            "classYY1",
            "classZZZ",
            "anotheRR9",
            "etc.";

        .make(length(@icons));
        .make(@i) when (@i > 0) {
            .make((@i - 1));
            @name_: e(extract(@icons, @i));
            .my-icon-@{name_} {
                #my-icon-bundle.myIcon(@row, @i);
            }
        }
    }
}

// ......
// usage:

#my-icon-bundle.icons();

P.S. All this is for LESS 1.5.x, I'm too lazy to make it compatible with earlier versions - sorry.

1 Comment

Thanks @seven-phases-max (sorry for late response) neat code :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.