I've encountered this before and I have a fair understanding of scope though not enough. I'm trying to pass a string as a parameter to an anonymous function, inside of a for loop. From my understanding by default this is recursive so no matter how many times the variable's string changes it still sends the same string (1,2,3,4 is passed on (maybe like?) 1,1,1,1 when we want 1,2,3,4).
I want to make this non-recursive so in the example below the file names from the multiple-file-input-element are different (since none of them can be the same).
How can we make the event listener receive the non-recursive parameter?
function images_upload(e)
{
for (var i = 0; i < document.getElementById('post_files').files.length; i++)
{
var file = document.getElementById('post_files').files[i];
var n = images_name(file.name);
var xhr = new XMLHttpRequest();
if (typeof xhr.upload=='object')
{
var upload = xhr.upload;
upload.addEventListener('progress', function(e,n)
{
alert('n = '+n);
}, false);
xhr.open('POST','upload.php');
xhr.setRequestHeader('Cache-Control','no-cache');
xhr.setRequestHeader('X-Requested-With','XMLHttpRequest');
xhr.setRequestHeader('X-File-Name',file.name);
xhr.send(file);
}
else {var ns = true;}
}
if (ns) {alert('Error: your browser does not support AJAX file uploads.');}
}
Yes, I know the context of what I'm testing (testing in Firefox 10, Chrome 17(?) and Opera 12).
Consider the following two snippets:
var i;
for (i = 0; i < 5; i += 1) {
setTimeout(function () {
// here, i will be the loop variable, and thus you'll get
// an ouput of 5, 5 times, because by the time this function will be
// called, the loop will be long finished
console.log(i);
}, 250);
}
for (i = 0; i < 5; i += 1) {
setTimeout(function (i) {
return function () {
// here, i will be the function parameter of the outer function
// which has been called immediately passing in a 'copy' of
// the loop variable.
// output will be: 0, 1, 2, 3, 4
console.log(i);
};
}(i), 250);
}
demo: http://jsfiddle.net/Hpxqq/
Also, this has nothing to do with recursion.
Regarding your code, you should change:
upload.addEventListener('progress', function(e,n)
{
alert('n = '+n);
}, false);
to:
upload.addEventListener('progress', function (n) {
return function(e) {
alert('n = ' + n);
};
}(n), false);
Javascript doesn't have block scope...
Try this :
function images_upload(e) {
var ns = false;
for (var i = 0; i < document.getElementById('post_files').files.length; i++) {
(function(i) {
var file = document.getElementById('post_files').files[i];
var n = images_name(file.name);
var xhr = new XMLHttpRequest();
if (typeof xhr.upload == 'object') {
var upload = xhr.upload;
upload.addEventListener('progress', function(e, n) {
alert('n = ' + n);
}, false);
xhr.open('POST', 'upload.php');
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('X-File-Name', file.name);
xhr.send(file);
}
else {
ns = true;
}
})(i);
}
if (ns) {
alert('Error: your browser does not support AJAX file uploads.');
}
}
Related
I have multiply functions which are using the same cycle code and i'm wondering is it possible to simplify the code by having one cycle function so i could execute the code just by calling wanted function names.
Now:
for(var i=0;i<all;i++){ someFunction(i) }
Need:
cycle(someFunction);
function cycle(name){
for(var i=0;i<all;i++){
name(i);
}
}
I tried to do this by using "window" and i get no error but the function is not executed.
var MyLines = new lineGroup();
MyLines.createLines(); // works
MyLines.addSpeed(); // doesn't work
var lineGroup = function(){
this.lAmount = 5,
this.lines = [],
this.createLines = function (){
for(var i=0,all=this.lAmount;i<all;i++){
this.lines[i] = new line();
}
},
this.addSpeed = function (){
// no error, but it's not executing addSpeed function
// if i write here a normal cycle like in createLines function
// it's working ok
this.linesCycle("addSpeed");
},
this.linesCycle = function(callFunction){
for(var i=0,all=this.lAmount;i<all;i++){
window['lineGroup.lines['+i+'].'+callFunction+'()'];
}
}
}
var line = function (){
this.addSpeed = function (){
console.log("works");
}
}
window['lineGroup.lines['+i+'].'+callFunction+'()'];
literally tries to access a property that starts with lineGroups.lines[0]. Such a property would only exist if you explicitly did window['lineGroups.lines[0]'] = ... which I'm sure you didn't.
There is no need to involve window at all. Just access the object's line property:
this.lines[i][callFunction]();
i get no error but the function is not executed.
Accessing a non-existing property doesn't generate errors. Example:
window[';dghfodstf0ap9sdufgpas9df']
This tries to access the property ;dghfodstf0ap9sdufgpas9df, but since it doesn't exist, this will result in undefined. Since nothing is done with the return value, no change can be observed.
Without a name space use:
window["functionName"](arguments);
SO wrap it up and use it thus:
cycle(someFunction);
function cycle(name){
for(var i=0;i<all;i++){
window[name](i);;
}
}
With a namespace, include that:
window["Namespace"]["myfunction"](i);
Note that this is likely a bit of overkill but using a function to make a class object (you can google the makeClass and why it is/could be useful) you can create instances of the object.
// makeClass - By Hubert Kauker (MIT Licensed)
// original by John Resig (MIT Licensed).
function makeClass() {
var isInternal;
return function (args) {
if (this instanceof arguments.callee) {
if (typeof this.init == "function") {
this.init.apply(this, isInternal ? args : arguments);
}
} else {
isInternal = true;
var instance = new arguments.callee(arguments);
isInternal = false;
return instance;
}
};
}
var line = function () {
this.addSpeed = function () {
console.log("works");
};
};
var LineGroup = makeClass();
LineGroup.prototype.init = function (lineNumber) {
this.lAmount = lineNumber?lineNumber:5,
this.lines = [],
this.createLines = function (mything) {
console.log(mything);
var i = 0;
for (; i < this.lAmount; i++) {
this.lines[i] = new line();
}
},
this.addSpeed = function () {
console.log("here");
this.linesCycle("addSpeed");
},
this.linesCycle = function (callFunction) {
console.log("called:" + callFunction);
var i = 0;
for (; i < this.lAmount; i++) {
this.lines[i][callFunction]();
}
};
};
var myLines = LineGroup();
myLines.createLines("createlines");
myLines.addSpeed();
//now add a new instance with 3 "lines"
var newLines = LineGroup(3);
newLines.createLines("createlines2")
console.log("addspeed is a:" + typeof newLines.addSpeed);
console.log("line count"+newLines.lAmount );
newLines.addSpeed();
I'm facing for the first time OOP in JavaScript and all the troubles that comes with it...
I have this function/Object/class/whatever which has a method mainLoop() that should display some falling text - just like in the movie The Matrix. When I call it though I get undefined variables errors and using the debugger I see that inside mainLoop() this is pointing to Window instead of the object that called the method.
Here's the code:
function Matrix(config) {
return {
//[...lots of other vars...],
drops: [],
lines: [],
//final string to put in the container
str: "",
mainLoop: function(){
var tmp = "";
//randomly create a "character drop"
//(not if there's already a drop)
for(var i = 0; i < this.cols; i++){
if(this.drops[i] == 0 && Math.random() < this.freq){
this.drops[i] = irandom(this.rows) + 1;//new drop
tmp += randomChar();//output drop
}
else tmp += lines[0].charAt(i);
}
this.lines[0] = tmp; // <-------------- ERROR
//update already created drops
tmp = "";
for(var j = 0; j < this.cols; j++){
if(this.drops[j] > 0){
tmp += this.randomChar();
this.drops[j]--;
}
else tmp += " ";
}
this.lines[this.rowIndex] = tmp;
this.rowIndex = (this.rowIndex+1) % this.rows;
//render the entire text
this.str = "";
for(var l in this.lines)
this.str += l + "<br/>";
$(container).html = this.str;
},
start: function(){
for(var i = 0; i < this.cols; i++)
this.drops[i] = 0;
timer = setInterval(this.mainLoop ,this.delay);
},
stop: function(){
clearInterval(this.timer);
},
randomChar: function(){
return this.chars.charAt(irandom(this.chars.length));
},
irandom: function(x){
return Math.floor(Math.random()*x);
}
}
};
And then I call this function like this:
var config = {
container: "#container",
rows: 20,
cols: 20,
delay: 2000
};
var m = Matrix(config);
m.start();
The browser console says:
TypeError: this.lines is undefined
(code comment shows the exact point of the error). Furthermore, the debugger says that, at that point, this points to Window, not to m as I would expect... what's wrong with my reasoning? Thanks in advance for any help.
Alter your start function:
start: function(){
var self = this;
for(var i = 0; i < this.cols; i++)
this.drops[i] = 0;
timer = setInterval(function() {
self.mainLoop();
}, this.delay);
}
this was poiting at window because the scope has changed.
Since JavaScript is prototype-based, maybe (if you haven't already) try doing it following this model:
function Matrix(config) {
this.property = config.firstmember;
this.property2 = config.secondmember;
return function() { console.log('hello world') };
}
Matrix.prototype = {
someMethod: function() {
//do something
},
start: function() {
//console.log('hello world');
},
stop: function() {
//do something
}
}
var config = {
firstMember: 'foo',
secondMember: 'bar'
}
var m = new Matrix(config);
//console output: "hello world"
/*var m = {
property: 'foo',
property2: 'bar',
____proto___: Matrix: {
someMethod: function() {
//do something
},
start: function() {
//console.log('hello world');
},
stop: function() {
//do something
}
}
}*/
Also, see the answer to this question regarding setInterval.
setInterval callback functions are members of the Window object; therefore, 'this' refers to the window. You will need to pass in a parameter of the current object to the callback that is inside setInterval. See the link above for more details.
If you need a reference to the calling object, I'd suggest passing it down as a parameter to the function.
I am working on a javascript code that looks like the following. I am only showing the basic skeleton of the code first.
var array = [];
function mainFun()
{
A();
}
function A()
{
//some code
B();
//code to print all values in "array"
}
function B()
{
C();
}
function C()
{
//some code
//push elements one by one in "array"
for (var i=0; i<10; i++)
{
array.push(i); //something on these lines
}
}
I know this code seems bizarre but this is exactly the situation I am working on. Thanks to Javascript's function level scoping (as against the regular block-level scoping), I am unable to access and print all the elements in A() that have been pushed in the array in C(). So how can I make my array variable work like a true global variable that has knowledge of what elements were pushed into it?
Ok, here is my original source code (I don't know how the dummy code worked though!)
var allLinks = {}; //set of all internal and external links
var Queued = [];
var crawlingURL;
var Crawled = [];
var xmlHttp = null, originURL, maxHops = 0, currentHop = 0;
function changeText(){
var tabID, sourceURL;
chrome.tabs.query({currentWindow: true, active: true}, function(tabs){
console.log(tabs[0].url);
document.getElementById('URL').innerHTML="URL of Current Page : "+tabs[0].url;
tabID = tabs[0].id;
sourceURL = tabs[0].url;
Queued.push(sourceURL); //push the origin link the Queued array to begin crawling
beginCrawl(sourceURL);
});
}
document.addEventListener('DOMContentLoaded', function () {
changeText();
});
function beginCrawl(url)
{
originURL = url;
maxHops = 2;
currentHop = 1;
var queueIndex = 0;
//httpGet(originURL);
while(queueIndex<1) //(currentHop <= maxHops)
{
crawlingURL = Queued[queueIndex];
//allPages[crawlingURL] = {url:url, state:"crawling", level:0, host:startingHost};
httpGet(crawlingURL);
Crawled.push(crawlingURL);
queueIndex++;
for(var j = 0; j < Queued.length; j++)
{
console.log(j+". "+Queued[j]+"\n");
}
}
}
function httpGet(theUrl)
{
xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, true );
xmlHttp.send( null );
xmlHttp.onreadystatechange = ProcessRequest;
}
function ProcessRequest()
{
if ( xmlHttp.readyState == 4 && xmlHttp.status == 200 ) // xmlHTTP success
{
var container = document.createElement("p");
container.innerHTML = xmlHttp.responseText;
var anchors = container.getElementsByTagName("a");
var list = [];
for (var i = 0; i < anchors.length; i++)
{
var href = anchors[i].href;
var exists = 0;
// to check for duplicate entries in the list
for(var j = 0; j < Queued.length; j++) // remove duplicates
if(Queued[j] == href)
exists = 1;
if (exists == 0)
{
Queued.push(href);
document.getElementById('printORGLinks').innerHTML += href + "<br />";
}
}
}
}
I am unable to get print the values in my Queued Array ! (As you may understand, this is a preliminary code for a web crawler of some sort. I need to get the list of all URLs pushed into the Queued array).
This works as you've entered it - fiddle here http://jsfiddle.net/LmFqq/1/
Output
some code executing in A()
some code executing in B()
adding elements in C()
printing in A()
[0,1,2,3,4,5,6,7,8,9]
Code
var array = [];
var el = document.getElementById('output');
mainFun();
function log(msg) {
el.innerHTML += msg + "<br />";
}
function mainFun()
{
A();
}
function A()
{
log("some code executing in A()");
B();
log("printing in A()");
log(JSON.stringify(array));
}
function B()
{
log("some code executing in B()");
C();
}
function C()
{
log("adding elements in C()");
for (var i=0; i<10; i++)
{
array.push(i); //something on these lines
}
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
JavaScript closures and variable scope
Assign click handlers in for loop
I have this script:
var MyClass = {
MyArray: new Array(0, 1, 2, 3, 4),
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
var cen = document.getElementById("cen_" + i); // It is an img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc1(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}
},
MyFunc2: function(id) {
alert(id);
}
}
My problem is that, at this line :cen.onclick = function() { MyClass.MyFunc1(i); }; the argument sent to MyFunc2 is always -1. The MyFunc1 function should create four images, each one with an onclick event. When you click on each image, the MyFunc2 function should show the corresponding i value. It looks like the i value is not "saved" for each event and image element created, but only its "pointer".
Thanks!
You should be familiar with the concept of JavaScript closures to understand why this happens. If you are, then you should remember that every instance of the
function() { MyClass.MyFunc1(i); };
function closure contains i's value of -1 (since it is the final value of this variable after the entire loop finishes executing.) To avoid this, you might either use bind:
cen.onclick = (function(i) { MyClass.MyFunc1(i); }).bind(null, i);
or use an explicitly created closure with the proper i value.
It's a normal case and misunderstand of closures, see this thread and you may get some clue, the simply way to fix this problem is to wrap your for loop body with an Immediate Invoked Function Expression
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}(i));
}
}
You are capturing a variable that changes inside the loop, so you always get the last value of i.
You can easily fix that by creating a closure:
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
})(i);
}
},
I want to make a function, like this.
For example:
function Logger() {
this.log = function(msg) {
console.log(msg);
}
}
And I want to use it in functions/modules etc, and that all works fine.
But the default console in my browser normally give the fileName + lineNumber.
Now when I abstract this functionality, the fileName and lineNumber is not where I put my instance.log(). Because it will say from where the console.log is being called, not the function itself.
So my question:
How can I get the correct information from where I want to use my logger?
Or give me, please, any tips to improve this functionality.
function Logger() {
this.log = console.log.bind(console);
}
I asked about this some time ago: Create shortcut to console.log() in Chrome.
Try using backtrace function like this one :
function printStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist += 0; //doesn't exist- that's the point
} catch (e) {
if (e.stack) { //Firefox
var lines = e.stack.split('\n');
for (var i = 0, len = lines.length; i & lt; len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
callstack.push(lines[i]);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
else if (window.opera & amp; & amp; e.message) { //Opera
var lines = e.message.split('\n');
for (var i = 0, len = lines.length; i & lt; len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i + 1]) {
entry += ' at ' + lines[i + 1];
i++;
}
callstack.push(entry);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf( & amp; quot;
function & amp; quot;) + 8, fn.indexOf('')) || 'anonymous';
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
output(callstack);
}
function output(arr) {
//Optput however you want
alert(arr.join('\n\n'));
}
Try assigning the function:
(function () {
window.log = (console && console.log
? console.log
: function () {
// Alternative log
});
})();
Later just call log('Message') in your code.