Variable scope in Sass for loops - css

In one of my SCSS scripts, I discovered that I had accidentally given a #for loop counter the same name as different, global variable, but everything still worked as expected.
For example, paste this example script into http://sassmeister.com/:
$w: white;
$r: red;
$b: blue;
$y: yellow;
//...
$test: '';
//Accidentally using an existing variable name ($r) as the counter:
#for $r from 1 through 10 {
#if($test != '') { $test: $test + ', '; }
$test: $test + $r;
}
.someclass {
/*Note: $r is 'red', not the #for counter. #for loops create their own scope?*/
color: $r;
/*All the #for counters. #for created a *local* $r, but accessed the *global* $test...*/
something: unquote($test);
}
...and the CSS will look like:
.someclass {
/*Note: $r is 'red', not the #for counter. #for loops create their own scope?*/
color: red;
/*All the #for counters. #for created a *local* $r, but accessed the *global* $test...*/
something: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
}
So, I would have thought that the #for loop changed $r from red to 10 by the time it was used in .someclass, but (luckily) it didn't. This indicates that the loop works in its own local scope, but it still accesses the global $test variable, even without using a !global flag.
And now I'm confused. The docs state that mixins have a local scope, but I haven't found any documentation about for loop scoping. Is this "hybrid" scoping a documented feature - the loop counter is in some local scope, while the loop "body" works in the parent scope - or is it a bug in the SCSS compiler?

I don't think this is a bug in the compiler as I believe this to be the intended behavior of the #for loop, but you're correct in that that it's not documented. I would submit a request to have this clarified / added into the official docs.

Related

Builtin for checking if vec contains specified element

Let's say, I have a vec<int> containing a list of integers that may be non-continuous (due to element being removed from database).
Example:
$occupiedNumbers = vec[1, 2, 3, 5, 6, 8, 10, 11, 12, 13, 15, 16];
Now what I need is to check if this vec contains a given number, and if yes, increase it by 1 and repeat checking.
$newItemNumber = count($occupiedNumbers);
while (/* check if $occupiedNumbers contains $newItemNumber */) {
$a++;
}
In vanilla PHP there is in_array() for this, but would that work for a vec? Also, there is a builtin HH\Vector::linearSearch() but it's meant to be used with vec's predecessor, HH\Vector.
What is the right solution to check if a vec contains a given value?
Neither HHVM nor Hack docs say about that use case for a vec. Also, I'm missing some sort of REPL tool to check it manually off-project without building a whole (possibly faulty) project.
The recommendation from the HHVM/HackLang guys is to use C\contains() as you can read here but you need to install the HSL package (hhvm/hsl) using composer. This library contains loads of functions to deal with the new array-type structures: vec, dict, keyset. Those functions in the HSL Library are prefixed with C, Vec, Dict, Keyset and you'll find also Math, Str, etc., very useful for other needs.
After installing that package it becomes available but typically it is more handy to add use namespace to avoid the long prefix:
use namespace HH\Lib\C;
This is how you can do it:
$exists = C\contains($occupiedNumbers, $newItemNumber);
If you are really looking for the longest continuous interval starting from count($occupiedNumbers) then it may be much faster to find its initial index in the sorted list (if it exists), then use direct indexing from there:
// `use namespace HH\Lib\{C, Vec};` at top level
$occupiedNumbers = vec[1, 2, 3, 5, 6, 8, 10, 11, 12, 13, 15, 16];
$sorted = Vec\sort($occupiedNumbers); // if not sorted in general
$size = count($sorted);
$v = $size;
$i = C\find_key($sorted, $w ==> $w === $v);
if($i !== null) {
for(; $i < $size - 1 && $sorted[$i + 1] === $v + 1; $i++, $v++) {
}
}
// $i is null | <index of largest number in continuous interval>

Combining a string and a variable value to access an existing variable

Imagine I have the following set of defined variables:
$sportmenu_1: #23765c;
$sportmenu_3: #5e6b34;
$sportmenu_4: #7d7e6c;
$sportmenu_6: #786857;
$sportmenu_8: #487a91;
I also have a list containing the "id" or the last digit of each variable as:
$list: 1 3 4 6 8;
I then want to the $sportmenu_ variables to both name the css selectors and populate the properties, I'm using a list and a foreach as follows:
#each $id in $list_id_sports {
#sportmenu_#{$id} { //this works fine
.menu-sport-item {
background-color: #{$sportmenu_}#{$id}; //here's the issue!
}
}
}
The problem is that I cannot find a way to combine a string and the $id from the foreach to generate a variable that the Sass compiler will understand. Basically, I want the foreach to create a new variable by combining $sportmenu_ and the value of $id. Not sure if this is possible.
My solution doesn't work, since $sportmenu_ doesn't exist. I've tried combining a string and the $id as: "$sportmenu_"#{$id} but this just creates a string followed by the value of $id.
Thanks!
Why can't you do something like this? Using maps
$list: (1: #23765c, 3: #5e6b34, 4: #7d7e6c, 6: #786857, 8: #487a91);
//$id grabs your map index, and $val grabs your hex color values
#each $id, $val in $list {
#sportmenu_#{$id} {
.menu-sport-item {
background-color: #{$val};
}
}
}
You can try it out on SassMeister. Which outputs :
#sportmenu_1 .menu-sport-item {
background-color: #23765c;
}
.....
All I am doing over here is nothing but using maps(kind of hash), looping over, and printing colors accordingly.
Also, what you were trying won't work. You are trying to interpolate two variables and trying to create a dynamic variable name on compile.
#{$sportmenu_}#{$id}; //will throw undefined $sprotsmenu_ error

How to create list output in less?

I am trying to create a list which is calculated from another list. For example, if I have a list like 1,2,3,4... then the output has to be 10,20,30,40... Is there any other way to create a list from another in less? Please refer the below codes.
#input: 1,2,3,4;
.for(#array) when (default()) {.for-impl_(length(#array))}
.for-impl_(#i) when (#i > 1) {.for-impl_((#i - 1))}
.for-impl_(#i) when (#i > 0) {.-each(extract(#array, #i), #i)}
.create-list(#input){
.for(#input); .-each(#value, #a) {
.output_#{a}(#a) { // Create dynamic mixin based on input list
#output_#{a}: #a * 10; // Create dynamic variable to store each calc
}
}
}
.create-list(#input);
.loop(#count) when (#count > 0){
#prev: #count - 1;
.loop(#prev);
.first() when (#count = 1){
.output_#{count}(); // call first mixin which created already
#res_#{count}: #output_#{count} // Store the result in another dynamic var
}
.first() when not (#count = 1){
.output_#{count}();
#res_#{count}: #res_#{prev}, #output_#{count}; // join prev and current result
}
.first();
}
.loop(4);
The above implementation similar I expect like below.
.a1(){
#o1: #fff;
}
.a2(){
#o2: #aaa;
}
.a3(){
#o3: #ccc;
}
.loop(#counter) when (#counter > 0) {
.loop((#counter - 1));
.a1();
#out1: #o1;
.a2();
#out2: #out1, #o2;
.a3();
#out: #out2, #o3;
div{
colors: #out;
}
}
.loop(1);
and the output is #fff, #aaa, #ccc.
You'll create a modified "list" by passing the concatenated result of each loop iteration to next iteration, thus the final iteration defines the actual result. E.g. (just illustrating the principle w/o adopting it to your use-case):
// usage:
#input: 1, 2, 3, 4;
result {
.modify-list(#input);
result: #result;
}
// impl:
.modify-list(#list) {
#n: length(#list);
.-(#n, extract(#list, #n) * 10);
.-(#i, #r) when (#i > 1) {
.-(#i - 1; extract(#list, #i - 1) * 10, #r);
}
.-(1, #r) {#result: #r}
}
Demo
Technically this code does not create a one dimensional list (i.e. the result there is not really equal to #result: 10, 20, 30, 40;) but since in final CSS it's rendered identically it would work just fine.
Also assuming your original use-case was How to apply less functions to gradient colors?, you don't really need a code like this (i.e. it's an "XY Problem" and instead of generating a new variable with the new list, direct generation of the corresponding gradient values would be much less verbose and much more readable. See the linked answer in comments of the mentioned question).

Less mixin scope problems

I built a parametric mixin that finds the children of an element based on a "node-value" system that I created. What happens is the children are found by a loop function ".extractArrays" with ".deliverChild" inside it. Then the children (up to 4 of them) are put into ".calculateWidth", which returns the width of the child in a variable (ex. "calculatedWidth1").
The problem is: when there are no third and fourth children, #child3 and #child4 are unset, which creates an error. I set #child3 and #child4 to zero before the mixins, hoping that this would give them default values of 0 before the mixins are called. For some reason, however, when there is a third child, the mixin's returned value of #child3 won't override the #child3 that is set to 0. I am unsure why this is happening, but I feel like there are a couple things that throw me off about Less when it comes to mixins and scope.
#child3: none, 0 0, 0, 0;
#child4: none, 0 0, 0, 0;
.extractArrays(#index, #node, #node-value, #elements-array) when (#index <= #length-elements-array) {
#element-array: extract(#elements-array, #index);
#found-node: extract(#element-array, 2);
.deliverChild(#element-array, #found-node) when (#found-node = #child-node-value1) {
#child1: extract(#element-array, 1);
}
.deliverChild(#element-array, #found-node) when (#found-node = #child-node-value2) {
#child2: extract(#element-array, 1);
}
.deliverChild(#element-array, #found-node) when (#found-node = #child-node-value3) {
#child3: extract(#element-array, 1);
}
.deliverChild(#element-array, #found-node) when (#found-node = #child-node-value4) {
#child4: extract(#element-array, 1);
}
.deliverChild(#element-array, #found-node);
.extractArrays(#index + 1, #node, #node-value, #elements-array);
}
.extractArrays(1, #node, #node-value, #elements-array);
.calculateWidth(#child1, 1);
.calculateWidth(#child2, 2);
.calculateWidth(#child3, 3);
width: #calculatedWidth1 + #calculatedWidth2 + #calculatedWidth3 + #calculatedWidth4;
(Not an exact answer to the "scope" question above but an algorithm to use as a starting point in an alternative approach suggested in comments above):
A generic array filtering mixin would look like this (Less 1.7.5 or higher):
#array: // key value
a 1,
b 2,
c 3,
a 4,
d 5,
a 6,
c 7;
// usage:
usage {
.filter-array(#array, a);
result: #filter-array;
}
// impl.:
.filter-array(#array, #key, #key-index: 1) {
.-(length(#array));
.-(#i, #r...) when (#i > 0)
and not(#key = extract(extract(#array, #i), #key-index)) {
// skip item since key does not match
.-((#i - 1), #r);
}
.-(#i, #r...) when (length(#r) > 0)
and (#key = extract(extract(#array, #i), #key-index)) {
// key matches and #r is not empty so concat current item to #r
.-((#i - 1); extract(#array, #i), #r);
}
.-(#i, #r...) when (length(#r) = 0)
and (#key = extract(extract(#array, #i), #key-index)) {
// key matches and #r is empty so this is our first item
.-((#i - 1), extract(#array, #i));
}
.-(#i, #r...) when (#i = 0) {
// define "return" variable:
#filter-array: #r;
}
}
With resulting array being equal to:
#filter-array:
a 1,
a 4,
a 6;
This looks quite scary and verbose but still more clean and more controllable than the original approach.
Additionally it would make sense to provide a dedicated mixin to return a sum (or any other "transformation" with result in a single value) of certain values of the array items (that way the implementation can be simplified to a certain point since you won't need to concatenate anything and some of above conditions and/or mixin specializations become unnecessary).

Xquery: Selecting n number of records at a time

I'm using XQUERY, I have 25000 records saved in Database, I want to select these records in chunks(1000), How do I get the records recursively?
Window queries
You could use the tumbling window for this purpose, sadly it isn't supported by many XQuery engines yet.
For the following suggestions, you have to wrap the results into XML as XQuery only knows flat sequences, no nested ones.
Building your own tumbling windows
You can build your own tumbling windows functions which could look like this one, it creates "window" elements containing $count "item" elements each:
declare function local:window($seq as item()*, $size as xs:integer) as item()* {
for $i in 1 to xs:integer(fn:ceiling(count($seq) div $size))
return
element {"window"} {
for $j in (($i - 1) * $size + 1) to $i*$size
return
element {"item"} {$seq[$j]}
}
};
local:window((2, 4, 6, 8, 10, 12, 14), 3)
Splitting sequences for recursive windows
If you want to solve this problem recursively, you will need some split function which sadly isn't available in standard xquery. You could use $n=1000, work with the "head" elements and call your "worker function" recursively with the "tail".
declare function local:split($seq as item()*, $n as xs:integer) as element()* {
(
element {"head"} {
for $i in subsequence($seq, 1, $n)
return element {"item"} {$i}
},
element {"tail"} {
for $i in subsequence($seq, $n+1)
return element {"item"} {$i}
}
)
};
local:split((2, 4, 6, 8, 10, 12, 14), 3)
To access the tail elements as a sequence, use
local:split((2, 4, 6, 8, 10, 12, 14), 3)[2]//item/data()

Resources