[GIS] OpenLayers dynamic rules defintion & clone function filter

javascriptopenlayers-2

I'm developing on a OpenLayers application which creates dynamic rules for a vector layer using the geostats library. I want to create these rules inside a simple loop through the number of classes. I'm pushing every rule to a array and add this array via stale.addRules(). In my opinion every rule has to be cloned before adding it to the array. The problem is, I am using a OpenLayers.Filter.Function in which the clone() function is not implemented yet (OpenLayers 2.12-rc4).

How can I solve this?

At the moment, only the last rule gets visualized. It seems like the other rules get lost, due to the reference value issue…

My code looks like this:

for (var i = 0; i < numClasses; i++) {
    filter_x = new OpenLayers.Filter.Function({
            evaluate: function(attributes) {
                [...]
            }
        })
    var rule_x = new OpenLayers.Rule({
        filter: filter_x,
        symbolizer: { fillColor: colors[i],
                    fillOpacity: 0.5, strokeColor: "white"}
    });
    //var clone = filter_x.clone(); //returns null! clone is not implemented
    rules.push(rule_x)

}
style.addRules(rules);

Best Answer

I think you hit a very common problem of imperative languages that have first-class functions as functional languages do but lack referential transparency. The result are (as a functional programmer would call it) broken closures. I hit this problem in Python some years ago, too and is was quite frustrating. Some weeks ago, this blog post hit my Google+ stream discussing the same issue.

As a sample, take the following code (please excuse I'm not demonstrating it using OpenLayers since I know nothing about OpenLayers and nearly nothing about JavaScript (so excuse the possible use of JS anti-patterns))

function foo_broken() {
    var xs = [];

    for (var i = 0; i < 10; i++) {
        var bar = function() { return i; }
        xs.push(bar);
    }

    return xs;
}

function foo_working() {
    var xs = [];

    for (var i = 0; i < 10; i++) {
        var bar = function(t) { return function() { return t; } };
        xs.push(bar(i));
    }

    return xs;
}

I think most users would expect foo_broken() and foo_working() to do exact the same thing, but no, not in JavaScript. The test code below alerts 10,10,10,10,10,10,10,10,10,10 when evaluating the list returned by foo_broken() and 0,1,2,3,4,5,6,7,8,9 for foo_working().

The problem is that the closure for the anonymous function assigned to bar does not contain the current value of i but a reference to the variable i. That's the reason it's always the last value assigned to that variable. It's IMHO broken but it's like JavaScript, Python 2 and some other languages do it :-(.

The pattern to fight this issue I always use is to define another function which takes as many arguments as the values I'd like to force. In the demo above it's only one parameter (t) because I simply want the value of i. So to force the evaluation of variable i I pass it as a parameter to the anonymous function inside the loop and so the JavaScript interpreter is forced to evaluate it inside the loop.

function alert_values(fun) {
    var bars = fun();
    value_strs = "";
    for (var i = 0; i < 10; i++) {
        value_strs += "" + bars[i]() + ",";
    }
    alert(value_strs);
}

alert_values(foo_broken);
alert_values(foo_working);