Javascript object property not accessible in callback function - javascript

I have a html page with this javascript in the body section of the page
JSFiddle link (I'm new to jsfiddle, couldn't get it to work, any help on this is appreciated to...)
The Firebug console shows me 0 in request, undefined in onError and 0 in callback. I have two questions, why can't i access this.loadErrors in onError, and how would I implement this error counter?
EDIT: the source code
var loader = new Loader();
loader.request(callback);
function callback(){
console.log("loader.loadErrors in callback: " + loader.loadErrors);
}
function Loader(){
this.loadErrors = 0;
this.request=function(callback){
for (var i = 0; i < 3; i++){
console.log("this.loadErrors in request: " + this.loadErrors);
$.ajax("example", {error: onError}).done(callback);
}
}
function onError(){
console.log("this.loadErrors in onError: " + this.loadErrors);
// this.loadErrors++;
callback();
}
}

The code messed up this keyword.
onError is called from outside of your code and it's this points to an unknown object (probably null).
To prevent this from happening, you have one of the two options:
bind() the onError() function to your Loader before calling it:
this.request=function(callback){
for (var i = 0; i < 3; i++){
console.log("this.loadErrors in request: " + this.loadErrors);
$.ajax("example", {error: onError.bind(this)}).done(callback);
}
}
Bind it to the Loader during creation:
var _this = this;
function onError(){
console.log("this.loadErrors in onError: " + _this.loadErrors);
// _this.loadErrors++;
callback();
}

Related

How to add a callback to phantomJS onLoadFinished

I'm trying to automate the navigation of some web pages with phantomJS.
What i'm trying to create is a pattern for testing and navigation, so far i got this.
For a moment ignore all the potential null pointers due to empty arrays and such :)
testSuite.js
var webPage = require('webpage');
// Test suite definition
function testSuite(name){
this.name=name;
this.startDate=new Date();
this.tests=[];
this.add=function(test){
this.tests.push(test);
};
this.start=function(){
console.log("Test Suite ["+this.name+"] - Start");
this.next();
},
this.next=function(){
console.log("neeext");
console.log(this.tests.length);
var test=this.tests[0];
this.tests.splice(0,1);
console.log("Test ["+ test.name+"]");
test.execute();
};
}
//Test definition
function test(name,testFunction){
this.name=name;
this.execute=testFunction;
}
module.exports.testSuite=testSuite;
module.exports.test=test;
FirstPageModule.js
var currentPage;
function onPageLoadFinished(status) {
var url = currentPage.url;
var filename='snapshot.png';
console.log("---------------------------------------------------------------");
console.log("Status: " + status);
console.log("Loaded: " + url);
console.log("Render filename:" + filename);
console.log("---------------------------------------------------------------");
if(status == 'success'){
currentPage.render(filename);
}
if(status=='fail'){
console.log("Status: " + status);
}
}
function open(){
currentPage.open("http://localhost:8080");
}
function login(){
var username="topSecretUsername";
var password="topSecretPassord";
currentPage.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js");
currentPage.evaluate(function(user,pass) {
$("#user").val(user);
$("#pass").val(pass);
},username,password);
currentPage.render("page.png");
currentPage.evaluate(function(){
$('#loginButton').click();
});
}
function FirstPage(){
var page = webPage.create();
currentPage=page;
this.testSuite = new testSuite("FirstPageModule");
this.testSuite.add(new test("Open First Page",open));
this.testSuite.add(new test("Login",login));
var onLoadFinished=onPageLoadFinished;
var callNextTest=this.testSuite.next;
currentPage.onLoadFinished=function(status){
onLoadFinished.apply(this,arguments);
callNextTest();
};
page.onConsoleMessage = function(msg) {
console.log(msg);
}
}
module.exports=new FirstPage();
PageTests.js
var firstPage=require('./FirstPageModule.js');
firstPage.testSuite.start();
What i want to do is to have a sequential execution of isolated functions, after each function gets executed, i take a screenshot and call the next function.
But, for some reason, the next method on the testSuite isn't getting called, or the method on the second test isn't getting executed.
What am i doing wrong?
Just make available the logName variable in the "global" scope :
var logName;
function onPageLoadComplete(status){
console.log(status);
// Call the logName function
if(typeof(logName) == "function"){
logName();
}
}
function test(){
var page = webPage.create();
this.name="TestName";
// Update logName value as a function.
logName = function(){
console.log(this.name);
}
page.onLoadFinished = onPageLoadComplete;
}
Primary, it doesn't seems to be related to phantomjs but only plain javascript, i hope that's what you need, otherwise please be more specific with your question.
You can create your own page.get implementation with a callback when a page is fully loaded.
ex: create a file module pageSupport.js
// attach listeners
Object.prototype.listeners = {};
// store and fire listeners
Object.prototype.addEventListener = function(event, callback) {
if (!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(callback);
this[event] = function(e) {
if (listeners[event]) {
listeners[event].forEach(function(listener) {
listener.call(null, e);
});
}
}
}
// create a new reference to webpage.open method
Object.prototype._open = Object.open;
// receive an url and
// return a function success that will be called when page is loaded.
Object.prototype.get = function(url) {
return {
success : function(callback) {
this.open(url);
this.addEventListener('onLoadFinished', function(status) {
if (status == 'success') {
return callback(status);
}
});
}.bind(this)
}
}
// export as phantomjs module.
exports.Object = Object;
So you can call this module in your script and uses it as follows:
var page = require('webpage').create();
require('./pageSupport');
page.get('http://stackoverflow.com').success(function(status) {
// Now this callback will be called only when the page is fully loaded \o/
console.log(status); // logs success
});

How to pass an array of objects from actionscript to javascript

AS3:
ExternalInterface.addCallback('getParams', getParams);
function getParams()
{
var params:Array = new Array();
for(i = 0; i < images.length; i++)
{
params.push(picWin.getChildAt(i));
}
return params;
}
JS:
$('#button').click(function(){
var res = document.getElementById("swfobject").getParams();
alert(res);
})
So after i get an error of some NPO object error, can't figure it out what it means, but if I pass an array itself its ok, if I pass an object itself it will be also ok, but when i pass an array of objects it gives me an error NPO, how to fix this?
To pass from AS to JS you want to use
ExternalInterface.call("myJsFunction", myArray);
for this example, you need 2 JS functions: the first handles the click and sends a request to your swf. The second is called by the swf with your return value:
AS3:
ExternalInterface.addCallback('getParams', getParams); // listens for JS to getParams
function getParams()
{
var params:Array = new Array();
for(i = 0; i < images.length; i++)
{
params.push(picWin.getChildAt(i));
}
ExternalInterface.call("handleParams", params); // calls a js function and passes params
}
JS:
$('#button').click(handleClick)
function handleClick(event){
document.getElementById("swfobject").getParams(); //sends request to swf
}
function handleParams(params){ // handles response from swf
alert("You got an array with " + params.length + " elements back from flash.");
}

pass loop parameters to closure inside closure

I'm trying to give the for loop parameter i to the inner closure because I want to identify my decoded audio (that's put inside buffer).
This code gives an error: e is undefined. It works however when removing the )(test) by which I mean that test is equal to list.length for all the results however I want them to have the value of the current parameter i when called.
for (var i = 0; i < list.length; i++) { //load in every url
requestArray.push(new XMLHttpRequest());
requestArray[i].open('GET', list[i].url, true);
requestArray[i].responseType = 'arraybuffer';
test = i;
requestArray[i].onload = (function (e) {
//Async method: ASK J
context.decodeAudioData(e.target.response, (function (buffer) { //Async method
console.log(test);
if (!buffer) {
alert('error decoding file data: ');
return;
}
})(test),
function (e) {
console.log('Error decoding audio file', e)
});
})(test);
requestArray[i].onerror = function () {
alert('BufferLoader: XHR error');
}
requestArray[i].send();
}
for (var i=0; i<list.length; i++){
requestArray.push(new XMLHttpRequest());
requestArray[i].open('GET', list[i].url, true);
requestArray[i].responseType = 'arraybuffer';
requestArray[i].onload = (function (i) {
return function (resp) {
// i: index in requestArray
// resp: the response object passed when the onload event occurs
context.decodeAudioData(
resp.target.response,
(function(test) {
return function (buffer) {
console.log(test);
if (!buffer) {
alert('error decoding file data: ');
return;
}
}
}(i)),
function(e) { console.log('Error decoding audio file', e)}
);
}
}(i));
requestArray[i].onerror = function() {
alert('BufferLoader: XHR error');
}
requestArray[i].send();
}
Please note that for a closure to be created a function must return a function.
This is a closure:
(function(){
var a = "b";
return function(){ alert(b); }
}());
This is evaluated AS SOON as it is seen:
(function(){
var a = "b";
}());
Your code does not do what you want it to. Instead of binding test as an argument, you immediately call your anonymous functions with the argument test and pass their returned results as arguments to decodeAudioData. I'd suggest fixing this first. E.g. by using Function.prototype.bind.
I guess I would try a syntax like this (I would also take a more modular approach, with several declared function)
for (var i=0; i<list.length; i++){ //load in every url
requestArray.push(new XMLHttpRequest());
requestArray[i].open('GET', list[i].url, true);
requestArray[i].responseType = 'arraybuffer';
test = i;
requestArray[i].onload = (function(outerIndex){
return function (e) { //Async method: ASK J
context.decodeAudioData(e.target.response,
(function(index){
return function(buffer) { //Async method
console.log(index);
if (!buffer) {
alert('error decoding file data: ');
return;
}
};
})(outerIndex), function(e) { console.log('Error decoding audio file', e)});
};})(test);
requestArray[i].onerror = function() {
alert('BufferLoader: XHR error');
}
requestArray[i].send();
}
When you put
})(test);
You run the function at that time (not when the event occurs) passed test like e.
I don't understand "e is undefined" e is i.
Could you try to put "var" where you define the variable test and remove ")(test)"? I think the issue is in scope of test.

Why is Javascript output being held back in Google Chrome?

I have javascript/jquery code which fetches info and updates it into the database with a mixture of while/for loops. While fetching, I have a div which shows a current progress log of whats going on. In Firefox, as the script is running it updates the div at the same time as it should. In Google Chrome, it runs the entire loop, holding back the log, and only outputs it until the script is finished running. Anyone have any idea why this is happening?
Here is my code:
$(document).ready(function() {
add_text("test");
var array_length = num_sets;
for(var i = 0; i < array_length; i = i + 1) {
var setId = sets[i]['id'];
var setName = sets[i]['name'];
var setLinkName = sets[i]['link'];
var setNumCards = sets[i]['num_cards'];
add_text("Beginning to fetch set \"" + setName + "\"");
add_text("Found " + setNumCards + " total cards.");
while(ii < setNumCards) {
var card_name = sets[i]['cards'][ii]['name'];
var card_link = sets[i]['cards'][ii]['link'];
add_text("Fetching card " + sets[i]['cards'][ii]['name']);
fetch_card(sets[i]['cards'][ii]['link'], setId);
}
}
});
add_text function:
function add_text(text) {
$("#status_text").append("<br />" + text);
}
fetch_card function:
function fetch_card(card_link, set_id)
{
$.ajax({
url: "fetch_card.php?link=" + card_link + "&set_id=" + set_id,
context: document.body,
async: false,
success: function(){
ii = ii + 1;
}
});
}
You are using synchronous ajax calls (which are generally not very desirable). The browser can block all activity until that ajax call completes. Whether or not the browser updates the screen during a synchronous ajax call is up to the browser.
Your code would be much better if it was rewritten to use asychronous ajax only. It takes a little more work to structure your code properly to work with asynchronous ajax calls, but the browser remains completely responsive during the asynchronous ajax calls.
I'm not entirely sure how you were using the ii variable in your original implementation (as it wasn't declared or initialized in the code you included), but this is the general structure you could use. It uses the traditional for loop to collect all the data you wanted in an array, then calls the ajax function one a time on that data. It isn't clear to me how you're actually doing anything with the returned ajax info, but perhaps that just isn't something you included here:
$(document).ready(function() {
add_text("test");
var array_length = num_sets;
var fetchData = [];
var fetchIndex = 0;
for(var i = 0; i < array_length; i++) {
var setId = sets[i]['id'];
var setName = sets[i]['name'];
var setLinkName = sets[i]['link'];
var setNumCards = sets[i]['num_cards'];
add_text("Beginning to fetch set \"" + setName + "\"");
add_text("Found " + setNumCards + " total cards.");
for (var ii = 0; ii < setNumCards; ii++) {
var card_name = sets[i]['cards'][ii]['name'];
var card_link = sets[i]['cards'][ii]['link'];
add_text("Fetching card " + sets[i]['cards'][ii]['name']);
fetchData.push({link: sets[i]['cards'][ii]['link'], id: setId});
}
}
function next() {
if (fetchIndex < fetchData.length) {
fetch_card(fetchData[fetchIndex].link, fetchData[fetchIndex].id, next);
fetchIndex++;
}
}
function fetch_card(card_link, set_id, successFn) {
$.ajax({
url: "fetch_card.php?link=" + card_link + "&set_id=" + set_id,
context: document.body,
async: true,
success: successFn
});
}
next();
});

How to return an array from a Javascript Facebook API request?

Hopefully there is an easy way to do this and my Javascript skills are just lacking. I am wanting to call a function that will get some Facebook posts, add them to an array and return to use elsewhere. Current code is below.
function GetFaceBookStream(name, max) {
FB.init({ apiKey: 'removed for post' });
var lastDate = '2011-04-29Z00:00:00';
var faceBookArray = [];
var faceBookString;
FB.api("/" + name + "/feed", { limit: max, since: lastDate }, function (response) {
var sb = string_buffer();
for (var i = 0; i < response.data.length; i++) {
var post = response.data[i];
sb.append("<li class='facebook'>");
sb.append("<img alt=\"Facebook\" src='Images\\Carousel\\fbIcon.png\' />");
sb.append("<h4>FACEBOOK</h4>\n");
sb.append("<div class=\"from-name\">" + post.from.name + "</div>");
sb.append("<div class=\"time\">" + post.created_time + "</div>");
if (post.message != undefined) {
sb.append("<div class=\"message\">" + post.message + "</div>");
}
sb.append("</li>stringSplitMarker");
}
faceBookString = sb.toString();
faceBookArray = faceBookString.split('stringSplitMarker');
});
return faceBookArray;
}
I realize this set up won't work due to variable scope in Javascript, but this is basically what I'm trying to achieve. Any help will be greatly appreciated!
You're making an asynchronous AJAX request.
The callback only runs after your code finishes.
You need to pass the data back using a callback.
For example:
function GetFaceBookStream(name, max, callback) {
...
FB.api(..., function(response) {
...
callback(something, else);
});
}
You can call the function by supplying a callback to receive the response:
GetFaceBookStream(name, max, function(param1, param2) {
//This code runs later and can use the response.
});

Categories