Clicking objects on the evaluated page using CasperJS - javascript

I'm trying to click x number of objects on the evaluated page but it's failing after all day sunday attempts.. I have the following code, you can see I build up a list of objects in my variable (called itemsToAdd) and then I need that to be passed onto the evaluated page and then those objects to be clicked.
I know you can't pass complex objects to the evaluated page but every attempt I've tried has failed.. I've tried everything, please help. I've also tried custom js files although I couldn't get that working either.
$(function(ns) {
ns.myMethod = function(itemArray) {
var items = itemArray.items;
for (i = 0; i < items.length; i++) {
casper.thenEvaluate(function() {
casper.click(items[i].buttonId);
casper.waitUntilVisible(items[i].basketId, function() {
casper.echo(items[i].successMessage);
});
});
}
return this;
};
})(namespace('test'));
This is my variable, the buttonId is the DOM id for the button on the evaluated page. The basketId is another section on the evaluated page that gets updated to represent the button clicking has worked.
Complex variable
var itemsToAdd = {
'items': [
{
buttonId: '#button1',
basketId: '#nowInBasket1',
successMessage: 'It worked'
},
{
buttonId: '#button2',
basketId: '#nowInBasket2',
successMessage: 'this worked aswell'
}
]
};
Calling the code
test.myMethod(itemsToAdd);

There are multiple problems with your code.
evaluate is the sandboxed page context. There are no CasperJS or PhantomJS functions accessible inside of it. But it doesn't look like you are using the page context, so you should change thenEvaluate to then. I have written post here which shows for which things you can use the evaluate function/page context.
JavaScript has function scope. Read this question to learn more. This means that after the loop has executed all is point to the last i. You need a closure to fix this (here I use an IIFE, but you can also change the loop to itemArray.items.forEach(...).
var items = itemArray.items;
for (i = 0; i < items.length; i++) {
(function(i){
casper.then(function() {
casper.click(items[i].buttonId);
casper.waitUntilVisible(items[i].basketId, function() {
casper.echo(items[i].successMessage);
});
});
})(i);
}
If this doesn't solve your problem then this is probably a problem with your $ framework, whatever that is.

Related

JavaScript callback function with relative variables

I am not entirely sure how to phrase this question, but basically, I have a class, button that on its click should call the function passed to it.
button = function(...,callBack) {
//...
this._cb = callBack;
}
button.prototype.callBack = function(e) {
//...
this._cb();
}
and then somewhere else
//on canvas click
e.target.callBack(e);
(I hope this is about the right amount of background, I can give more if needed)
So the issue I am running into is when I dynamically instantiate the buttons such that their callbacks use data from an array. i.e.
for (var i = 0; i < levels.length; i++) {
buttons[buttons.length] = new button(..., function() {drawLevel(levels[i])});
}
Then when they are clicked, they run that callback code and try to find some random value for i (probably a for-loop that didn't use var) and runs that level.
My question is, how can I (without using eval) circumvent this problem.
Thanks!
I'm not 100% clear on what you're asking, but it looks like you're going to be getting the wrong value for i in the anonymous function you're creating in the loop (it will always be levels.length)
Way around this is to have a different scope for every function created, with the i in each scope being a copy of the i in the loop
buttons[buttons.length] = new button(..., (function(i){
return function() {drawLevel(levels[i])};
})(i));

Animating an algorithm in Javascript

I am using Raphael.js to visualize a convex hull algorithm.
However I want to be able to step through the different parts of the code (or use something like sleep()/delay()). However, I can't see a way of accomplishing this using setTimeOut(). Any ideas?
For example:
sort(points);
//sleep(...)/delay(...)/pause until click?
for(...) {
message('Foo thing');
//sleep(...)/delay(...)/pause until click?
while() {
message('Comparing points');
//sleep(...)/delay(...)/pause until click?
}
}
In JavaScript there is no way to suspend code execution with sleep function. Executing JavaScript code is designed to be non-blocking.
Solution with using debugger keyword works on Chrome as well. You just have to open Developer Tools.
I prepared demo which works in different way. It simulates sleep function using setInterval and does not block scripts execution. However, it involves some additional code.
Let's assume that we have initial code:
var arr = [0, 1, 2, 3, 4];
function step(val) {
console.log(val);
}
for (var i = 0, len = arr.length; i < len; i++) {
step(arr[i]);
}
Now, we'd like to rewrite it so that each log shows after one second:
var arr = [0, 1, 2, 3, 4],
steps = [];
function step(val) {
console.log(val);
}
for (var i = 0, len = arr.length; i < len; i++) {
steps[i] = step.bind(null, arr[i]);
}
var int = setInterval(function() {
var fun = steps.shift();
if(!fun) {
clearInterval(int);
return;
}
fun();
}, 1000);
Let me explain it a little bit. Firstly, I define steps array, where I put new functions with bound arguments. bind function basically creates new function with arguments which are bound to provided values. More details on MDN page.
Example:
function step(a) { console.log(a); }
var step1 = step.bind(null, 1);
// now step1 is like `var step1 = function() { console.log(1); }`
In for loop I create and put new functions using bind. The last step is to extract these functions from steps array, starting from beginning (using Array.prototype.shift method), and execute them with interval equal to 1 second.
I know it's not a direct solution of your problem, but I hope it helps you convert your code properly. If you decide to do so, I advise to convert code blocks within for and while loops to functions. It simplifies conversion a little bit.
You could try to use the debugging tools available on your browser. If you are on chrome, enable the developer tools by pressing Shift + Ctrl + I. If you are on firefox, you could download install the firebug extension. Once you have done this, you can step through your code by putting in place a breakpoint. This is done by putting the 'debugger;' keyword at the javascript point where you want to begin stepping through. E.g
sort(points);
debugger
for(...) {
message('Foo thing');
debugger;
while() {
message('Comparing points');
debugger;
}
}
Maybe you could wait for a button click and then when the click has happened you could step one line of code in?
set a onclick listener for the button and set the variable continue = true;
after the piece of code has executed and you want to wait for the next piece of code to run you could use.
//code just executed
while(continue == false) sleep(10);
continue = false;
//next code to be executed
while(continue == false) sleep(10);
continue = false;
//more code....
there is probably a better solution than this so don't take this code as the best unless its your only answer.

Issue with execution order in javascript

I'm relatively new to javascript so please hold it against me.
I have a bit of code which should give the user a little time to reach the submenu from the base-menu.
My problem is that the code keeps executing in a weird order.
Here is the code:
function onFocusOut() {
var tester = 0;
setTimeout(function(){menuReset(tester)},1000);
}
function menuReset(tester) {
var hoverCheck = function (event) {
alert("#navBase a has focus"); //is fired, but to late...
var tester = event.data.varTester;
var tester = 1;
};
jQuery('#navBase').on('mousemove', 'a', { varTester: tester }, hoverCheck);
jQuery('#navBase').off('mousemove', 'a', { varTester: tester }, hoverCheck);
alert(tester); //This keeps firing first, before the alert in hoverCheck
if(tester == 1){
alert("tester = 1");
return;
}
else {
jQuery('#navBase ul').hide();
jQuery('#navBase').css({'width': ''});
jQuery('#navBaseAnchor').css({
'width': '', 'color': '',
'font-size': '',
'border-bottom-style': '',
'border-bottom-width': '',
'border-bottom-color': ''});
tester = 0;
}
}
Now I keep getting the alert that "tester" is 0, before the hoverCheck function is executed (which should set "tester" to 1) and fires the alert within that function.
Could someone tell me what I'm doing wrong?
I am also fairly new to JS, but should you also be watching out for variable scope errors too?
You have declared tester locally in onFocusOut() and in menuReset(tester), and then called it as a global var outside?
From answers.oreilly.com
LOCAL - Are those that are specific to a function and only work on it.
GLOBAL - Are those that are not defined within a function and may also serve to functions unless the function has not required that
variable.
Nevermind people...
I found a way around it all.
Currently i'm setting a .focus() to the anchor involved on mouseOver. (and of course blur() on mouseleave)
Then it's real easy to check the currently focussed element using document.activeElement.
So problem solved, altough in a bit different way.
alert(tester) is the first line of code that is executing something you notice as a user. The two function calls jQuery().on() and jQuery().off() are only attaching event handlers. If you want to see a "1" in the alert, you have to quickly move your mouse before hoverCheck is executed. But probably you cannot move your hand faster than JavaScript reaching the next line, which is the alert() with tester equals "0".
A little bit different approach would be to set a Javascript timeout() to make the submenu disappear after a certain amount of time if a certain condition isn't met.
Check out this JSFiddle example
Best of luck!

Javascript Closures and *static* classes problem

I have a static class which contains an array of callback functions, I then have a few other classes that are used to interact with this static class...
Here is a simple example of the static class:
var SomeStaticInstance = {};
(function(staticInstance) {
var callbacks = {};
staticInstance.addCallback = function(callback) { callbacks.push(callback); }
staticInstance.callAllCallbacks = function() { /* call them all */ }
}(SomeStaticInstance));
Then here is an example of my other classes which interact with it:
function SomeClassOne() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someCallback); }
this.someCallback = function() { /* Do something */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
function SomeClassTwo() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someOtherCallback); }
this.someOtherCallback = function() { /* Do something else */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
Now the problem I have is that when I call either class and tell it to activateCallbacks() the classes only activate the callbacks within their own scope, i.e SomeClassOne would call someCallback() but not someOtherCallback() and vice versa, now I am assuming it is something to do with the scope of the closures, however I am not sure how to get the behaviour I am after...
I have tried turning the static class into a regular class and then passing it into the 2 classes via the constructor, but still get the same issue...
So my question is how do I get the classes to raise all the callbacks
-- EDIT --
Here is an example displaying the same issue as I am getting on my actual app, I have put all script code into the page to give a clearer example:
http://www.grofit.co.uk/other/pubsub-test.html
It is a simple app with 2 presenters and 2 views... one view is concerned with adding 2 numbers at the top of the page, the 2nd view is concerned with taking that total and multiplying it and showing a result.
The 3rd party library I am using is PubSubJS, and the first presenter listens for an event to tell it that the one of the boxes has changed and re-totals the top row. The 2nd presenter listens for when the multiply or total at the top changes, then recalculates the bottom one. Now the first presenter recalculates correctly, and the 2nd presenter will correctly recalculate whenever the multiply box changes, HOWEVER! It will NOT recalculate when the total on the top changes, even thought it should receive the notification...
Anyway take a quick look through the source code on the page to see what I mean...
First, I think you want var callbacks = [] (an array instead of an object) since you're using callbacks.push().
I'm not sure I understand your problem. The way your classes are structured, you can achieve what you want by instantiating both classes and calling addCallbackToStaticInstance() on both new objects. E.g.,
var one = new SomeClassOne();
var two = new SomeClassTwo();
one.addCallbackToStaticInstance();
two.addCallbackToStaticInstance();
one.activateCallbacks();
Then, as above, you can call activateCallbacks() from either object.
If you're saying you want to be able to call activateCallback() after instantiating only one of the classes, you really have to rethink your approach. I'd start with moving addCallbackToStaticInstance() and activateCallbacks() into their own class.
This is a very odd way of doing things, but your main problem is that your callbacks object it not part of SomeStaticInstance, it is defined within an anonymous closure. Also your callbacks object {} should be an array [].
try staticInstance.callbacks = []; instead of var callbacks = {};
and
staticInstance.addCallback = function(callback) {
this.callbacks.push(callback);
}

JQuery not a function error

While developing with Firebug I keep getting this error.
pages[x].css("z-index",x) is not a function
The function itself works fine but I'm trying to figure out why it keeps flagging this. The function is simultaneously reorganizing the array and the z-indexes.
Can i not access array variables and call functions on them like this or is this something else?
full code:
var pages = $("#use-wrapper").children("div");
pages.children("a.right").click(function(event) {
event.preventDefault();
$(this).parent("div").css("z-index","0");
pages.push($(this).parent("div"));
for(var x = pages.length; x >= 1; --x) {
pages[x] = pages[x-1];
pages[x].css("z-index",x);
}
pages[0] = pages.pop();
});
If you do an alert(pages[x]), you'll find that each pages[x] is a DOM element and not a jQuery object, which is why you get the error that pages[x].css is not a function. You probably want to do:
$(pages[x]).css('z-index', x);
Edit: Even though jQuery lets you access the elements of pages as though it's an array, it's not a true array object, so I doubt that push and pop will work too.

Categories

Resources