Forgive me for what might be terrible javascript code. This is my first time trying something in javascript...
function Tile (window_id, size)
{
this.window_id = window_id;
this.size = size;
};
function Desktop ()
{
this.tiles = [];
this.ntiles = function () {return this.tiles.length;};
this.size = function ()
{
var sum = 0;
for (i = 0; i < this.ntiles(); i++) {sum += this.tiles[i].size;};
return sum;
};
this.addTile = function (tile)
{
if (this.size() === 1) {return -1;};
this.tiles.push(tile);
return 0;
};
};
function Layer ()
{
this.desktops = [];
this.ndesktops = function () {return this.desktops.length;};
this.addDesktop = function (desktop)
{
this.desktops.push(desktop);
return 0;
};
this.availableDesktopSize = function (size)
{
for (i = 0; i < this.ndesktops(); i++)
{
print(this.desktops[i].size());
print('hi');
print(this.desktops[i].size());
print('hihi');
var space = 1.0 - this.desktops[i].size();
print('hihihi');
print(space);
print(size);
if (space >= size) {return i;};
};
return -1;
};
};
var layer = new Layer();
var desktop1 = new Desktop();
var desktop2 = new Desktop();
var tile = new Tile(100, 0.5);
desktop1.addTile(tile);
desktop1.addTile(tile);
desktop2.addTile(tile);
layer.addDesktop(desktop1);
layer.addDesktop(desktop2);
print(layer.availableDesktopSize(0.51));
print(layer.availableDesktopSize(0.49));
I'm trying to make a method for the Layer class that finds the first desktop that has enough space left. In trying to achieve this, while trying my code, I observed that for some reason when I call the desktop.size() property I get the right value back the first time but when I call it a second time my script dies. This is the output
1
hi
TypeError: Result of expression 'this.desktops[i]' [undefined] is not an object.
So it does the first print fine but why can't it do the exact same function another time?
(If you have any other advice to improve my code, that would be very helpfull)
The problem is that you are using a global i variable in several loops.
You call a method from within such a loop, and that method has its own loop giving a different value to the same i variable. So when you come back from that call i no longer is the same as before.
Solution: declare your variables as local variables.
for (let i = 0; // ...etc
// ^^^
Use var to declare your iterator variable i to bind it to the scope. Now you're using a global scoped i that is causing trouble.
However in modern browsers let would be better because that binds the variable to the block scope. The current {} preventing the value of that variable to be used outside of that block.
function Tile (window_id, size)
{
this.window_id = window_id;
this.size = size;
};
function Desktop ()
{
this.tiles = [];
this.ntiles = function () {return this.tiles.length;};
this.size = function ()
{
var sum = 0;
for (var i = 0; i < this.ntiles(); i++) {sum += this.tiles[i].size;}; //var i binds i to this function scope.
return sum;
};
this.addTile = function (tile)
{
if (this.size() === 1) {return -1;};
this.tiles.push(tile);
return 0;
};
};
function Layer ()
{
this.desktops = [];
this.ndesktops = function () {return this.desktops.length;};
this.addDesktop = function (desktop)
{
this.desktops.push(desktop);
return 0;
};
this.availableDesktopSize = function (size)
{
for (var i = 0; i < this.ndesktops(); i++) //var i binds i to this function scope.
{
console.log(this.desktops[i].size());
console.log('hi');
console.log(this.desktops[i].size());
console.log('hihi');
var space = 1.0 - this.desktops[i].size();
console.log('hihihi');
console.log(space);
console.log(size);
if (space >= size) {return i;};
};
return -1;
};
};
var layer = new Layer();
var desktop1 = new Desktop();
var desktop2 = new Desktop();
var tile = new Tile(100, 0.5);
desktop1.addTile(tile);
desktop1.addTile(tile);
desktop2.addTile(tile);
layer.addDesktop(desktop1);
layer.addDesktop(desktop2);
console.log(layer.availableDesktopSize(0.51));
console.log(layer.availableDesktopSize(0.49));
I'm not positive but it seems to be the way i is assigned in your for loops.
in this bit of code
print(this.desktops[i].size());
print('hi');
print(this.desktops[i].size());
i is 0 but then is set to 2 in the line below
for (i = 0; i < this.ntiles(); i++) {sum += this.tiles[i].size;};
function Tile (window_id, size)
{
this.window_id = window_id;
this.size = size;
};
function Desktop ()
{
this.tiles = [];
this.ntiles = function () {return this.tiles.length;};
this.size = function ()
{
var sum = 0;
for (i = 0; i < this.ntiles(); i++) {sum += this.tiles[i].size;};
return sum;
};
this.addTile = function (tile)
{
if (this.size() === 1) {return -1;};
this.tiles.push(tile);
return 0;
};
};
function Layer ()
{
this.desktops = [];
this.ndesktops = function () {return this.desktops.length;};
this.addDesktop = function (desktop)
{
this.desktops.push(desktop);
return 0;
};
this.availableDesktopSize = function (size)
{
for (i = 0; i < this.ndesktops(); i++)
{
console.log(this.desktops[i].size());
console.log(i)
print('hi');
print(this.desktops[i].size());
print('hihi');
var space = 1.0 - this.desktops[i].size();
print('hihihi');
print(space);
print(size);
if (space >= size) {return i;};
};
return -1;
};
};
var layer = new Layer();
var desktop1 = new Desktop();
var desktop2 = new Desktop();
var tile = new Tile(100, 0.5);
desktop1.addTile(tile);
desktop1.addTile(tile);
desktop2.addTile(tile);
layer.addDesktop(desktop1);
layer.addDesktop(desktop2);
console.log(layer.availableDesktopSize(0.51));
console.log(layer.availableDesktopSize(0.49));
Related
This function is make "cards" array in target object, and I added some codes to draw each element in "cards" array(see the mark : this part), but it doesn't work. How can i do?
var player = {
cards = [];
};
function giveNCards(cardsArr, target, n) {
target.cards = [];
for (var i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
/////////// this part //////////
for (var i = 0; i < target.cards.length; i++) {
var cardImage = new Image();
cardImage.onload = (function(value) {
return function() {
ctx.drawImage(this, i * 100, 0);
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
//////////////////////////////////////////
console.log(target.cards);
return target.cards;
}
cardImage is getting overwritten on each loop.
Variables (when defined with the 'var' keyword) are local to the function (function scope) and get 'hoisted' (moved to the top). What does that mean? Basically, your code will run like this:
function giveNCards(cardsArr, target, n) {
var i, cardImage // <-- cardImage defined first here (hoisted)
target.cards = [];
for (i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
for (i = 0; i < target.cards.length; i++ ){
cardImage = new Image();
cardImage.onload = (function(value){
return function(){
ctx.drawImage(this, i * 100, 0);
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
console.log(target.cards);
return target.cards;
}
The simplest fix for this (though there are others) is to move the contents of your second for loop to another function which will give it it's own scope.
function giveNCards(cardsArr, target, n) {
var i
target.cards = [];
for (i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
for (i = 0; i < target.cards.length; i++ ){
renderCard(target, i)
}
console.log(target.cards);
return target.cards;
}
function renderCard(target, i) {
var cardImage = new Image();
cardImage.onload = (function(value){
return function(){
ctx.drawImage(this, i * 100, 0);
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
Pay attention, the i value that you used inside the IFFE is from the global scope, therefore you always modifies the same value
var player = {
cards = [];
};
function giveNCards(cardsArr, target, n) {
target.cards = [];
for (var i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
/////////// this part //////////
for ( var i = 0; i < target.cards.length; i++ ){
var cardImage = new Image();
cardImage.onload = (function(value){
return function(){
ctx.drawImage(this, value * 100, 0);
// -------------------^
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
//////////////////////////////////////////
console.log(target.cards);
return target.cards;
}
My case it this:
function s () {
this.funcs = [];
this.funcs.addF = function (str) {
/* this will push a function to the funcs array, which uses getCoordX() and getPixelY() */
this.push (Function("pixelX", "var x = getCoordX(pixelX); var f = " + str + "; return getPixelY(f);"));
}
function getCoordX(a){
return 0;
}
function getPixelY(a){
return 0;
}
}
As you can see, in that array I'm adding functions that are created from strings, and those functions need do use getCoordX() and getPixelY(), which are in the s() object. When I try to access them it gives this error: Uncaught ReferenceError: getCoordX is not defined.
What should I do to make it work? Please help.
Edit 2
How i would use this code:
function s () {
this.funcs = [];
this.funcs.addF = function (str) {
/* this will push a function to the funcs array, which uses getCoordX() and getPixelY() */
this.push (Function("pixelX", "var x = getCoordX(pixelX); var f = " + str + "; return getPixelY(f);"));
}
this.drawCanvas = function() {
//some code goes here
this.drawGraph(c);
}
this.drawGraph = function (c) {
c.lineWidth = 2;
var cnt = 0; //count how many pixels have been rendered
for(var i = this.limitLeft; i < this.limitRight; i+= this.pixelwidth) {
for(var u = 0; u < this.funcs.length; u++) {
var f = this.funcs[u];
//some if statements go here
}
}
}
function getCoordX(a){
return 0;
}
function getPixelY(a){
return 0;
}
}
var canvas = document.createElement("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.id = "canvas";
document.body.appendChild(canvas);
var c = new Canvas("canvas");
c.funcs.addF("2*x");
c.drawCanvas();
You might do this:
function s () {
this.funcs = [];
this.funcs.addF = function (str) {
/* this will push a function to the funcs array, which uses getCoordX() and getPixelY() */
this.push (Function("pixelX", "getCoordX", "getPixelY", "var x = getCoordX(pixelX); var f = " + str + "; return getPixelY(f);"));
}
this.drawCanvas = function() {
//some code goes here
this.drawGraph(c);
}
this.drawGraph = function (c) {
c.lineWidth = 2;
var cnt = 0; //count how many pixels have been rendered
for(var i = this.limitLeft; i < this.limitRight; i+= this.pixelwidth) {
for(var u = 0; u < this.funcs.length; u++) {
var f = this.funcs[u];
var currvalue = f(i, getCoordX, getPixelY);
var lastvalue = f(i-1, getCoordX, getPixelY);
//some if statements go here
}
}
}
function getCoordX(a){
return 0;
}
function getPixelY(a){
return 0;
}
}
var canvas = document.createElement("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.id = "canvas";
document.body.appendChild(canvas);
var c = new Canvas("canvas");
c.funcs.addF("2*x");
c.drawCanvas();
This will do it. Hope this helps ;)
this isn't implicit in JavaScript, you must precise it.
Also don't use a string to create a function, just use
this.funcs.addF = function (str) {
var obj = this;
/* this will push a function to the funcs array, which uses getCoordX() and getPixelY() */
this.push (function(pixelX){
var x = obj.getCoordX(pixelX);
return obj.getPixelY(str);
});
}
The problem is that the Function constructor creates functions which run in the global scope. So your function can't access your getCoordX in the closure.
You could make getCoordX and getPixelY global functions:
function getCoordX(a) {
return a;
}
function getPixelY(a) {
return a;
}
function s() {
this.funcs = [];
this.funcs.addF = function (str) {
this.push(new Function("pixelX",
"var x = getCoordX(pixelX);" +
"var f = " + str + ";" +
"return getPixelY(f);"
));
};
}
var obj = new s();
obj.funcs.addF('x*3 + 5');
console.log(obj.funcs[0](1)); // 8
Alternatively, you could use the Function constructor only to evaluate str, and move the other code outside.
function s() {
this.funcs = [];
this.funcs.addF = function (str) {
var f = new Function('x', 'return ' + str);
this.push(function(pixelX) {
var x = getCoordX(pixelX);
return getPixelY(f(x));
});
};
function getCoordX(a) {
return a;
}
function getPixelY(a) {
return a;
}
}
var obj = new s();
obj.funcs.addF('x*3 + 5');
console.log(obj.funcs[0](1)); // 8
Here you have a full example with canvas:
function s() {
this.funcs = [];
this.funcs.addF = function (str) {
var f = new Function('x', 'return ' + str);
this.push(function(pixelX) {
var x = getCoordX(pixelX);
return getPixelY(f(x));
});
};
this.drawGraph = function(c) {
c.lineWidth = 2;
for(var u = 0; u < this.funcs.length; u++) {
var f = this.funcs[u];
c.beginPath();
c.moveTo(0, 200-f(0));
for(var x=1; x<400; ++x) c.lineTo(x, 200-f(x));
c.stroke();
}
};
function getCoordX(a) {
return a;
}
function getPixelY(a) {
return a;
}
}
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 200;
document.body.appendChild(canvas);
var c = new s("canvas");
c.funcs.addF(".5*x");
c.funcs.addF("x + 50");
c.funcs.addF("3*x + 100");
c.drawGraph(canvas.getContext('2d'));
This issue is asked already some times. But in the application of google apps script i can't solve this problem.
function two ()
{
var bridgeclubs = SpreadsheetApp.openById("1dfyI1jbz..........TVg4OoixKTz1");
var bridgeclubs_sheet = bridgeclubs.getSheetByName("Sheet1");
var data_bridgeclubs = bridgeclubs_sheet.getDataRange().getValues();
var numRows = bridgeclubs_sheet.getDataRange().getNumRows();
var sprshtname = SpreadsheetApp.getActiveSpreadsheet().getName();
for (var i=1; i<=numRows; i++)
{
if (sprshtname == data_bridgeclubs[i][6])
{
var A = i;
break;
}
}
}
In function one I do a call to function two, where I need this value A:
function one ()
{
two ();
//here I need this value A;
var C= 35 * A;for instance
}
Who can help me?
You can return value from function using return i; and obtain it in within one by var A = two();.
function two() {
var bridgeclubs = SpreadsheetApp.openById("1dfyI1jbz..........TVg4OoixKTz1");
var bridgeclubs_sheet = bridgeclubs.getSheetByName("Sheet1");
var data_bridgeclubs = bridgeclubs_sheet.getDataRange().getValues();
var numRows = bridgeclubs_sheet.getDataRange().getNumRows();
var sprshtname = SpreadsheetApp.getActiveSpreadsheet().getName();
for (var i=1; i<=numRows; i++) {
if (sprshtname == data_bridgeclubs[i][6]) {
return i;
}
}
}
function one() {
var C;
var A = two();
if (A) {
C = 35 * A;
}
}
You can use global variables.
var A;
function two() {
var bridgeclubs = SpreadsheetApp.openById("1dfyI1jbz..........TVg4OoixKTz1");
var bridgeclubs_sheet = bridgeclubs.getSheetByName("Sheet1");
var data_bridgeclubs = bridgeclubs_sheet.getDataRange().getValues();
var numRows = bridgeclubs_sheet.getDataRange().getNumRows();
var sprshtname = SpreadsheetApp.getActiveSpreadsheet().getName();
for (var i=1; i<=numRows; i++) {
if (sprshtname == data_bridgeclubs[i][6]) {
A = i;
break;
}
}
}
function one() {
two();
var C= 35 * A;
}
I'm appending onclick events to elements that I'm creating dynamically. I'm using the code below, this is the important part only.
Test.prototype.Show= function (contents) {
for (i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
}
}
First it says that it's undefined. Then I changed and added:
var content = content[i];
menulink.onclick = function () { return that.ClickContent.apply(that, [content]); };
What is happening now is that it always append the last element to all onclick events( aka elements). What I'm doing wrong here?
It's a classical problem. When the callback is called, the loop is finished so the value of i is content.length.
Use this for example :
Test.prototype.Show= function (contents) {
for (var i = 0; i < contents.length; i++) { // no need to have <= and -1
(function(i){ // creates a new variable i
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function () { return that.ClickContent.apply(that, [contents[i]]); };
})(i);
}
}
This immediately called function creates a scope for a new variable i, whose value is thus protected.
Better still, separate the code making the handler into a function, both for clarity and to avoid creating and throwing away builder functions unnecessarily:
Test.prototype.Show = function (contents) {
for (var i = 0; i <= contents.length - 1; i++) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = makeHandler(i);
}
function makeHandler(index) {
return function () {
return that.ClickContent.apply(that, [contents[index]]);
};
}
};
A way to avoid this problem altogether, if you don't need compatibility with IE8, is to introduce a scope with forEach, instead of using a for loop:
Test.prototype.Show = function (contents) {
contents.forEach(function(content) {
var menulink = document.createElement('a');
menulink.href = "javascript:;";
menulink.onclick = function() {
return that.ClickContent.call(that, content);
};
});
}
I am using anonymous function assigned to a variable to minimize use of global variables. Within this function there are nested functions: one to preload and resize images, and two other nested functions for navigation (next and previous). The code below generates error that the variable to which the anonymous function is assigned is not defined:
Cannot read property 'preload_and_resize' of undefined
If you spot the problem please let me know. Thank you very much.
<html>
<head>
<script type="text/javascript">
var runThisCode=(function(){
var myImages=new Array("img/01.jpg","img/02.jpg","img/03.jpg");
var imageObj = new Array();
var index=0;
var preload_and_resize=function(){
var i = 0;
var imageArray = new Array();
for(i=0; i<myImages.length; i++) {
imageObj[i] = new Image();
imageObj[i].src=myImages[i];
}
document.pic.style.height=(document.body.clientHeight)*0.95;
};
var next_image=function(){
index++;
if(index<imageObj.length){
document.pic.src=imageObj[index].src;
}
else{
index=0;
document.pic.src=imageObj[index].src;
}
};
var prev_image=function(){
index--;
if(index>=0){
document.pic.src=imageObj[index].src;
}
else{
index=myImages.length-1;
document.pic.src=imageObj[index].src;
}
};
})();
</script>
</head>
<body onload="runThisCode.preload_and_resize();">
<div align="center">
<img name="pic" id="pic" src="img/01.jpg"><br />
PrevNext
</div>
</body>
</html>
Your anonymous function doesn't return anything, so when you run it, undefined gets returned. That's why runThisCode is undefined. Regardless though, with the way you've written it, preload_and_resize will be local, so you wouldn't be able to access that anyway.
Instead, you want this anonymous function to construct an object, and reutrn that. Something like this should work, or at least get you close:
var runThisCode=(function(){
var result = {};
result.myImages=new Array("img/01.jpg","img/02.jpg","img/03.jpg");
result.imageObj = new Array();
result.index=0;
result.preload_and_resize=function(){
var i = 0;
var imageArray = new Array();
for(i=0; i< result.myImages.length; i++) {
imageObj[i] = new Image();
imageObj[i].src=myImages[i];
}
document.pic.style.height=(document.body.clientHeight)*0.95;
};
result.next_image=function(){
index++;
if(index<imageObj.length){
document.pic.src=imageObj[index].src;
}
else{
index=0;
document.pic.src=imageObj[index].src;
}
};
result.prev_image=function(){
index--;
if(index>=0){
document.pic.src=imageObj[index].src;
}
else{
index=myImages.length-1;
document.pic.src=imageObj[index].src;
}
};
return result;
})();
This should explain what you are doing wrong :
var foobar = (function (){
var priv1, priv2 = 'sum' , etc;
return {
pub_function: function() {},
another: function() {
console.log('cogito ergo ' + priv2 );
}
};
})();
foobar.another();
You've assigned the function to the variable next_image which is scoped to the self-invoking anonymous function.
The value you assign to runThisCode is the return value of that anonymous function, which (since there is no return statement) is undefined.
To get the code to work you need to assign an object to runThisCode and make next_image a member of it.
Add the following to the end of the anonymous function:
return {
"next_image": next_image
}
Remove the anonymous function, and make your function public. You will only create one global variable: the object runThisCode.
var runThisCode = function () {
var myImages = new Array("img/01.jpg", "img/02.jpg", "img/03.jpg");
var imageObj = new Array();
var index = 0;
this.preload_and_resize = function () {
var i = 0;
var imageArray = new Array();
for (i = 0; i < myImages.length; i++) {
imageObj[i] = new Image();
imageObj[i].src = myImages[i];
}
document.pic.style.height = (document.body.clientHeight) * 0.95;
};
this.next_image = function () {
index++;
if (index < imageObj.length) {
document.pic.src = imageObj[index].src;
} else {
index = 0;
document.pic.src = imageObj[index].src;
}
};
this.prev_image = function () {
index--;
if (index >= 0) {
document.pic.src = imageObj[index].src;
} else {
index = myImages.length - 1;
document.pic.src = imageObj[index].src;
}
};
};
And then, later in your code:
runThisCode.preload_and_resize();
should work.
From the invocation you've got in body onload property, it looks like what you're trying to achieve with the IIFE (immediately invoked function expression) is return an object that has a the method preload_and_resize.
As others have pointed out, you're not returning anything from the IIFE, so really all that's happening is you're closing up everything inside it in its own namespace, but not "exporting" anything.
If you want to "export" those functions, from your IIFE, you'd probably add a final bit to it that looked something like this:
return {
'preload_and_resize': preload_and_resize,
'next_image': next_image,
'prev_image': prev_image
}
which essentially creates a new JavaScript object literal, and then assigns its properties to the function values from the local scope.
Some developers would find this redundant and rather than finishing out with this sort of explicit export would probably just define the functions while declaring the object literal, something like:
return {
preload_and_resize: function(){
var i = 0;
var imageArray = new Array();
for(i=0; i<myImages.length; i++) {
imageObj[i] = new Image();
imageObj[i].src=myImages[i];
}
document.pic.style.height=(document.body.clientHeight)*0.95;
},
next_image: function() {
index++;
if(index<imageObj.length){
document.pic.src=imageObj[index].src;
}
else {
index=0;
document.pic.src=imageObj[index].src;
}
},
prev_image: function() {
index--;
if(index>=0){
document.pic.src=imageObj[index].src;
}
else {
index=myImages.length-1;
document.pic.src=imageObj[index].src;
}
}
}
In respect of previous answers, my version:
function(self) {
let myImages = new Array("img/01.jpg", "img/02.jpg", "img/03.jpg");
let imageObj = new Array();
let index = 0; // if you need to expose this call with self.index
self.preload_and_resize = function() {
let i = 0;
let imageArray = new Array();
let (i = 0; i < myImages.length; i++) {
imageObj[i] = new Image();
imageObj[i].src = myImages[i];
}
document.pic.style.height = (document.body.clientHeight) * 0.95;
};
var next_image = function() {
index++;
if (index < imageObj.length) {
document.pic.src = imageObj[index].src;
} else {
index = 0;
document.pic.src = imageObj[index].src;
}
};
var prev_image = function() {
index--;
if (index >= 0) {
document.pic.src = imageObj[index].src;
} else {
index = myImages.length - 1;
document.pic.src = imageObj[index].src;
}
};
})(window.myCurrentPage = window.myCurrentPage || {});
// now you canll myCurrentPage.preload_and_resize();