2

I have the following scoping problem in a CasperJS script. baseTargetUrl is undefined when passing into casper.thenOpenAndEvaluate(). Why is this and how can I work around it?

var baseTargetUrl;
        .....
casper.then(function() {
    baseTargetUrl = this.evaluate(function() {
        return __utils__.getElementByXPath('//*[@id="wrapper"]/div[1]/a[2]')["href"];
    });
    console.log('logging: '+baseTargetUrl); // works
});

casper.thenOpenAndEvaluate(baseTargetUrl ,function() { //baseTargetUrl is undefined here
    var test = document.querySelector('myselector');
    //do other stuff

});
codecowboy
  • 9,835
  • 18
  • 79
  • 134

4 Answers4

3

As you know we can't grab variable from outside async calls. This seem kinda hacky but this is the best I've got for now ....

var baseTargetUrl;
        .....
casper.then(function() {
    baseTargetUrl = this.evaluate(function() {
        return __utils__.getElementByXPath('//*[@id="wrapper"]/div[1]/a[2]')["href"];
    });
    console.log('logging: '+baseTargetUrl); // works

    this.thenOpenAndEvaluate(baseTargetUrl ,function() { // 'this' being the instance of casper
        var test = document.querySelector('myselector');
        //do other stuff

    });
});
dcodesmith
  • 9,590
  • 4
  • 36
  • 40
  • Thanks! Why is it that you can't grab variables from 'outside'? Is it because you can't guarantee that they will exist yet? – codecowboy Jan 05 '14 at 15:29
  • A casper suite begins execution when you run `casper.run()`. At this point you already called/scheduled `thenOpenAndEvaluate` with `undefined`. Nesting seems to be the best way as casperjs handles it very well. – Artjom B. Jun 28 '14 at 17:13
1

A commonly used method (for good reasons) for dealing with this problem is using a promise.

There are many different implementations of promises. A lot of frameworks have their own promises included, such as jQuery and AngularJS. There are also stand alone promise frameworks, such as Q.

Promises are a way of chaining methods by resolving values. Once resolved the next function in the chain will be called.

When you'd use Q, your code could look like:

var baseTargetUrl = Q.defer();
        .....
casper.then(function() {
    var value;
    baseTargetUrl.resolve(value = this.evaluate(function() {
        return __utils__.getElementByXPath('//*[@id="wrapper"]/div[1]/a[2]')["href"];
    }));
    console.log('logging: ' + value); // works
});

baseTargetUrl.then(function (value) {
    casper.thenOpenAndEvaluate(value, function () { // value contains the result of the call above
        var test = document.querySelector('myselector');
        //do other stuff
    });
});

Promises are a way of dealing with async code to prevent it from becoming spaghetti, to keep things sane.

In a small situation like this, simply nesting the functions could be your solution too.

var baseTargetUrl;
        .....
casper.then(function() {
    baseTargetUrl = this.evaluate(function() {
        return __utils__.getElementByXPath('//*[@id="wrapper"]/div[1]/a[2]')["href"];
    });
    console.log('logging: '+baseTargetUrl); // works

    casper.thenOpenAndEvaluate(baseTargetUrl ,function() { //baseTargetUrl is no longer undefined, it's a closure now
        var test = document.querySelector('myselector');
        //do other stuff

    });
});
Aidiakapi
  • 6,034
  • 4
  • 33
  • 62
  • Hey, I like your promises solution. Little problem though, seems like we can't load the `q` module and run it as a casperjs script. The only way to run it seems to be via `phantomjs-node`. Any ideas? – dcodesmith Jan 05 '14 at 14:59
  • Thanks! can you explain what 'the problem' is? Is it that the baseTargetUrl variable is not populated by the time I try and use it? – codecowboy Jan 05 '14 at 15:26
  • @dcodesmith I'm sorry I'm not quite aware of how CasparJS runs, but I can't imagine there isn't a way to load external JS in the environment, since that'd be quite the limitation. Node could indeed solve this problem. – Aidiakapi Jan 05 '14 at 15:30
  • @codecowboy, the problem is that caspen.then is an asynchronous function and you simple can't access a variable set inside the function from the outside of the said function – dcodesmith Jan 05 '14 at 15:30
  • @codecowboy That is exactly the problem. The `then` function takes a callback, which it will call later than the code under it. Most likely if you'd use a timeout of like 1 second, it'd no longer be undefined, this however would be a race condition and could cause all sorts of troubles. Promises resolve this. – Aidiakapi Jan 05 '14 at 15:31
  • @dcodesmith do you think you could use Q as in the underscore example here - http://docs.casperjs.org/en/latest/writing_modules.html – codecowboy Jan 05 '14 at 16:31
  • @codecowboy I already installed the `q` node module but I couldn't run it using the `casperjs`, there seemed to be problem loading the module. – dcodesmith Jan 05 '14 at 16:35
1

How about using waitFor?

var baseTargetUrl;

casper.then(function() {
    baseTargetUrl = this.evaluate(/**/);
});

casper.waitFor(function() {
    return typeof baseTargetUrl !== "undefined";
}, function() { 
    var test = document.querySelector('myselector');
    // ...
});
NiKo
  • 11,215
  • 6
  • 46
  • 56
  • thanks! Feel free to take a look at http://stackoverflow.com/questions/20937627/how-can-i-wait-for-a-socket-io-connection-to-return-data-from-casperjs too ;) – codecowboy Jan 05 '14 at 22:50
0

This simply worked for me:

var dre_num = "12345678";  // global
casper.getdre = function() {
    return dre_num;
}
casper.setdre = function(str) {
    dre_num = str;
}

casper.then(function(){
    this.setdre("01833946");
});
casper.then(function(){
    this.echo("dre_num : " + this.getdre());
});