3

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);

2 Answers 2

1

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);
1
  • You definitely identified the problem of my code! Thank you for the detailed explanations. It is up and running now. ;)
    – rjw
    Commented May 29, 2012 at 20:00
0

i dont know whether this works or not, but you can try to apply other filter functions' clone object to OpenLayers.Filter.Function or you can write your own clone method for this function...

the following codes are examples and not tested yet.

clone: function() {
       return OpenLayers.Util.extend(new OpenLayers.Filter.Function(), this);

or

clone: function() {
        var params= [];
        for(var i=0, len=this.params.length; i<len; ++i) {
            params.push(this.params[i].clone());
        }
        return new OpenLayers.Filter.Function({
            name: this.name,
            params: filters
        });
    }

i hope it helps you...

1
  • Thank you for your help! I was successful in cloning the filter and the rule, but it did not solve the problem.After some passes with Firebug, I recognized that the evaluate()-function of the filters of the rules in the rules array reference to my function above. But the definition of this function changes with every loop cycle. Is there another or better way to use a changing OpenLayers.Filter.Function in a loop?
    – rjw
    Commented May 28, 2012 at 13:52

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.