Multiple instances of constructor not working - javascript

When I run this program with only one instance of Cat (var cat1), it works perfectly. When I run a second instance (var cat2), things stop working.
function main() {
var catPic = document.getElementById('catPic');
var numClicks = 0;
var numCats = -1;
var scope;
var Cat = function Cat(name, url) {
numCats++;
scope = this;
this.name = name;
this.url = url;
this.numClicks = 0;
this.clickId = 'clicks' + numCats;
window.onload = function() {
//Puts a new Cat on the screen. The name, image, and # of clicks will be shown.
appendCat(scope.name, scope.url, scope.clickId, scope.numClicks, numCats);
}
};
var cat1 = new Cat('Bob', 'https://lh3.ggpht.com/nlI91wYNCrjjNy5f-S3CmVehIBM4cprx-JFWOztLk7vFlhYuFR6YnxcT446AvxYg4Ab7M1Fy0twaOCWYcUk=s0#w=640&h=426');
var cat2 = new Cat('Samantha', 'https://lh3.ggpht.com/nlI91wYNCrjjNy5f-S3CmVehIBM4cprx-JFWOztLk7vFlhYuFR6YnxcT446AvxYg4Ab7M1Fy0twaOCWYcUk=s0#w=640&h=426');
}
main();
The problem has something to with the appendCat(); function.
//Puts a new Cat on the screen. The name, image, and # of clicks will be shown.
function appendCat(name, url, clickId, numClicks, numCats) {
document.body.appendChild(document.createElement('div'));
document.getElementsByTagName('div')[numCats].setAttribute('id', 'cat' + numCats);
document.body.getElementById().appendChild(document.createElement('h2'));
document.getElementsByTagName('h2')[numCats].textContent = name;
document.body.appendChild(document.createElement('img'));
document.getElementsByTagName('img')[numCats].setAttribute('id', name);
document.getElementsByTagName('img')[numCats].setAttribute('src', url);
document.body.appendChild(document.createElement('p'));
document.getElementsByTagName('p')[numCats].setAttribute('id', clickId);
document.getElementsByTagName('p')[numCats].textContent = numClicks;
document.body.appendChild(document.createElement('br'));
document.body.appendChild(document.createElement('br'));
}
When these first two lines of this function are run, document.getElementsByTagName('div')[numCats] evaluates to undefined. I'm not sure why this happens. Console.log() isn't helping me much either. Thanks!

First your scope variable needs to be declared within the constructor so that window.onload is referring to the correct instance. Otherwise, both window.onload handlers will refer to whatever value of scope was set to at the time window.onload triggers — which will always be the second instance. Second, instead of setting window.onload, you need to use window.addEventListener("load", function () { ... }") so that each instance has it's own listener. The window object only has one onload property so setting it multiple times doesn't create multiple listeners it just overwrites the previous ones.
As an additional note, I would suggest removing the event handling from the constructor and placing it outside something like this:
var cats = [
new Cat(...),
new Cat(...)
];
window.addEventListener('load', function () {
cats.forEach(function (cat) {
appendCat(cat.name, cat.url, cat.clickId, cat.numClicks, cats.length);
});
});
This would remove the scope and numCats variables and move all the window.onload funtionality to one handler instead of having one handler for each instance.

Related

Javascript object function call wont work

I'm working on a HTML5 soundboard, but i've hit a bit of a snag...
I'm trying to get a stop function to work on all of the sounds at once that are played. Unfortunatly when i call this function from a buttonpress, the object doesn't appear to have a stop function. The code for the actual sound element is the following:
// Container to keep all sounds in one place. This is a Dictionary within a dictionary to be able to search by catagory.
var sounds = {};
// Counter to keep track of unique ID's
var lastID = 0;
// Base class for initializing Any class
var Base = function(methods){
var base = function() {
this.initialize.apply(this, arguments);
};
for (var property in methods) {
base.prototype[property] = methods[property];
}
if (!base.prototype.initialize) base.prototype.initialize = function(){};
return base;
};
//Complete class for the Sound object. Generates its own DIV's and HTML5 tags to play stuff.
var Sound = Base({
// Init all the variables.
initialize: function(name, file, target='Sounds') {
this.name = name;
this.file = file
this.button = null;
this.audioelement;
this.id = lastID + 1;
this.target = target;
lastID ++;
// Check if the catagory is there, if not: create it with a placeholder object
var catagory = sounds[this.target];
if(catagory == null){
sounds[this.target] = {99:null};
}
sounds[this.target][this.id] = this;
// Call init function
this.init();
},
play : function() {
obj = this
if(obj.audioelement.paused == true){
obj.audioelement.play();
}else{
obj.audioelement.pause();
obj.audioelement.fastSeek(0);
}
},
stop : function(){
obj = this;
obj.audioelement.pause();
},
init : function(){
// Statement for JS class based shenanigans.
obj = this
// Create a button and add some text to it
obj.button = document.createElement("BUTTON");
obj.button.appendChild(document.createTextNode(obj.name));
// Set ID's and names to keep track of this button
obj.button.id = obj.id;
obj.button.name = obj.target;
// Get or create parent element. Used for catagory based display
var el = getOrCreateElement(obj.target)
el.appendChild(obj.button);
// Create audio element and set appropriate settings
obj.audioelement = document.createElement("AUDIO");
obj.audioelement.src = obj.file;
obj.audioelement.name
obj.button.appendChild(obj.audioelement);
// Add function to play/pause to button
obj.button.onclick = buttonClicked;
});
function buttonClicked(){
// Fetch sound from dicionary container using the name and id from the button [SET AT SOUND.INIT()]
var sound = sounds[this.name][this.id];
// Call the play function in [SOUND]
sound.play();
}
And for the stopall function:
function stopAll(){
// Scroll through the entire dictionary
for ( var key in sounds){
for ( var id in sounds[key]){
// Check if the sound is not a placeholder
if(id == 99){
continue;
}
// Call stop function with fetched object.
var sound = sounds[key][id];
sound.stop();
}
}
}
The weird thing is is that the play function does seem to work, but not the stop function. It says that the object doesn't have that specific function...
Any ideas would be appriciated!
WM
for ( var x in object) loops over a few more properties than just methods, including ones on the base object's prototype.
If you console.log(id); within that inner loop you will see the extra ones.
Try adding this inside your inner for loop:
if (typeof id !== 'function') {
continue;
}
Edit: this isn't quite correct but I'm not in a position to see what it should be right now. It's almost there, from memory! Keep playing with it.

Javascript Typedef Error when using parameters

What am I doing wrong, and how can one pass variables to a different function within the same wrapping variable/function.
Example:
function customFunctionWrap(){
this.myVar1 = 0;
this.getCurrentPosition = function(){
if (navigation.geolocation) {
navigator.geolocation.getCurrentPosition(function(position){});
}
},
this.doSomething = function(){ // Works
//Do something, return
this.callWithParams(); //Works
},
//If I remove passing in 'value1',calling it elsewhere works
this.doSomethingWithParams = function(value1){
//Use value1
//Return
},
this.callWithParams = function(){
var value1 = 'xyz'; //Is a variable that changes based on some DOM element values and is a dynamic DOM element
this.doSomethingWithParams(value1); //THROWS TYPEDEF ERROR: this.doSomethingWithParams is not a function
this.getCurrentPosition();
}
};
var local = new customFunctionWrap();
local.doSomething(); //WORKS
I know there is another way to do it and then directly use customFunctionWrap.callWithParams(), but am trying to understand why the former approach is erroring out.
var customFunctionWrap = {
myVar1 : 0,
callWithParams : function(){
}
}
What JS sees:
var customFunctionWrap = (some function)()
returned function is fired, because the last (), so it has to yield/return something, otherwise, like in your code it is "returning" undefined.
So your given code does not work.
The very first fix is to delete last 2 characters from
var customFunctionWrap = (some function)()
to make it return constructor.

local this variable is retaining old value even after clearing

I am facing a weird issue with "this". I have the code as follow which is for a page. In this I am creating a page and binding the onclick events. Here I reset the self[key] everytime I call funcOne but onClick I am setting the data.
The second time when I call funcOne and again I call onClick the data in this[key] is the old data instead of resetting.
Please suggest if I am doing anything wrong here. I am new to Javascript.
classExample = function(page) {
someBaseClass.call(this, page);
}
classExample.prototype.funcOne = function() {
var self = this;
var callback = function(data) {
self[key] = []; //based on some logic I am setting
};
model.getData(callback);
someButton.onClick = function() {
self.funcTwo();
};
};
classExample.prototype.funcTwo = function() {
//function from other class is called and data to this[key] is set
classTwo.someMethod(this);
var savedData = this[key];
};
var obj = new classExample(page);
obj.funcOne();
//after this I invoke onClick event
PS : I am not using any third party library.

JavaScript closures and variable scope

I am having trouble with JS closures:
// arg: an array of strings. each string is a mentioned user.
// fills in the list of mentioned users. Click on a mentioned user's name causes the page to load that user's info.
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// cause the page to load info for this screen name
newAnchor.onclick = function () { loadUsernameInfo(mentions[i]) };
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", ""); // unhide. hacky hack hack.
}
Unfortunately, clicking on one of these anchor tags results in a call like this:
loadUserNameInfo(undefined);
Why is this? My goal is an anchor like this:
<a onclick="loadUserNameInfo(someguy)">someguy</a>
How can I produce this?
Update This works:
newAnchor.onclick = function () { loadUsernameInfo(this.innerHTML) };
newAnchor.innerHTML = mentions[i];
The "i" reference inside the closure for the onclick handlers is trapping a live reference to "i". It gets updated for every loop, which affects all the closures created so far as well. When your while loop ends, "i" is just past the end of the mentions array, so mentions[i] == undefined for all of them.
Do this:
newAnchor.onclick = (function(idx) {
return function () { loadUsernameInfo(mentions[idx]) };
})(i);
to force the "i" to lock into a value idx inside the closure.
Your iterator i is stored as a reference, not as a value and so, as it is changed outside the closure, all the references to it are changing.
try this
function fillInMentioned(mentions) {
var mentionList = document.getElementById("mention-list");
mentionList.innerHTML = "";
for (var i = 0; i < mentions.length; i++) {
var newAnchor = document.createElement("a");
// Set the index as a property of the object
newAnchor.idx = i;
newAnchor.onclick = function () {
// Now use the property of the current object
loadUsernameInfo(mentions[this.idx])
};
// give this anchor the necessary content
newAnchor.innerHTML = mentions[i];
var newListItem = document.createElement("li");
newListItem.appendChild(newAnchor);
mentionList.appendChild(newListItem);
}
document.getElementById("mentions").setAttribute("class", "");
}

How do I run an object's method onEvent in javascript?

I just started using javascript and I'm missing something important in my knowledge. I was hoping you could help me fill in the gap.
So the script I'm trying to run is suppose to count the characters in a text field, and update a paragraph to tell the user how many characters they have typed. I have an object called charCounter. sourceId is the id of the text area to count characters in. statusId is the id of the paragraph to update everytime a key is pressed.
function charCounter(sourceId, statusId) {
this.sourceId = sourceId;
this.statusId = statusId;
this.count = 0;
}
There is one method called updateAll. It updates the count of characters and updates the paragraph.
charCounter.prototype.updateAll = function() {
//get the character count;
//change the paragraph;
}
I have a start function that is called when the window loads.
function start() {
//This is the problem
document.getElementbyId('mytextfield').onkeydown = myCounter.updateAll;
document.getElementbyId('mytextfield').onkeyup = myCounter.updateAll;
}
myCounter = new charCounter("mytextfield","charcount");
window.onload = start;
The above code is the problem. Why in the world can't I call the myCounter.updateAll method when the event is fired? This is really confusing to me. I understand that if you call a method likeThis() you'll get a value from the function. If you call it likeThis you are getting a pointer to a function. I'm pointing my event to a function. I've also tried calling the function straight up and it works just fine, but it will not work when the event is fired.
What am I missing?
Thanks for all the answers. Here's three different implementations.
Implementation 1
function CharCounter(sourceId, statusId) {
this.sourceId = sourceId;
this.statusId = statusId;
this.count = 0;
};
CharCounter.prototype.updateAll = function() {
this.count = document.getElementById(this.sourceId).value.length;
document.getElementById(this.statusId).innerHTML = "There are "+this.count+" charactors";
};
function start() {
myCharCounter.updateAll();
document.getElementById('mytextfield').onkeyup = function() { myCharCounter.updateAll(); };
document.getElementById('mytextfield').onkeydown = function() { myCharCounter.updateAll(); };
};
myCharCounter = new CharCounter('mytextfield','charcount');
window.onload = start;
Implementation 2
function CharCounter(sourceId, statusId) {
this.sourceId = sourceId;
this.statusId = statusId;
this.count = 0;
};
CharCounter.prototype.updateAll = function() {
this.count = document.getElementById(this.sourceId).value.length;
document.getElementById(this.statusId).innerHTML = "There are "+ this.count+" charactors";
};
CharCounter.prototype.start = function() {
var instance = this;
instance.updateAll();
document.getElementById(this.sourceId).onkeyup = function() {
instance.updateAll();
};
document.getElementById(this.sourceId).onkeydown = function() {
instance.updateAll();
};
};
window.onload = function() {
var myCounter = new CharCounter("mytextfield","charcount");
myCounter.start();
};
Implementation 3
function CharCounter(sourceId, statusId) {
this.sourceId = sourceId;
this.statusId = statusId;
this.count = 0;
};
CharCounter.prototype.updateAll = function() {
this.count = document.getElementById(this.sourceId).value.length;
document.getElementById(this.statusId).innerHTML = "There are "+this.count+" charactors";
};
function bind(funcToCall, desiredThisValue) {
return function() { funcToCall.apply(desiredThisValue); };
};
function start() {
myCharCounter.updateAll();
document.getElementById('mytextfield').onkeyup = bind(myCharCounter.updateAll, myCharCounter);
document.getElementById('mytextfield').onkeydown = bind(myCharCounter.updateAll, myCharCounter);
};
myCharCounter = new CharCounter('mytextfield','charcount');
window.onload = start;
I think you are having problems accessing your instance members on the updateAll function, since you are using it as an event handler, the context (the this keyword) is the DOM element that triggered the event, not your CharCounter object instance.
You could do something like this:
function CharCounter(sourceId, statusId) {
this.sourceId = sourceId;
this.statusId = statusId;
this.count = 0;
}
CharCounter.prototype.updateAll = function() {
var text = document.getElementById(this.sourceId).value;
document.getElementById(this.statusId).innerHTML = text.length;
};
CharCounter.prototype.start = function() {
// event binding
var instance = this; // store the current context
document.getElementById(this.sourceId).onkeyup = function () {
instance.updateAll(); // use 'instance' because in event handlers
// the 'this' keyword refers to the DOM element.
};
}
window.onload = function () {
var myCharCounter = new CharCounter('textarea1', 'status');
myCharCounter.start();
};
Check the above example running here.
The expression "myCounter.updateAll" merely returns a reference to the function object bound to "updateAll". There's nothing special about that reference - specifically, nothing "remembers" that the reference came from a property of your "myCounter" object.
You can write a function that takes a function as an argument and returns a new function that's built specifically to run your function with a specific object as the "this" pointer. Lots of libraries have a routine like this; see for example the "functional.js" library and its "bind" function. Here's a real simple version:
function bind(funcToCall, desiredThisValue) {
return function() { funcToCall.apply(desiredThisValue); };
}
Now you can write:
document.getElementById('myTextField').onkeydown = bind(myCounter.updateAll, myCounter);
You can:
function start() {
//This is the problem
document.getElementbyId('mytextfield').onkeydown = function() { myCounter.updateAll(); };
document.getElementbyId('mytextfield').onkeyup = function() { myCounter.updateAll(); };
}
In ASP.Net Ajax you can use
Function.createDelegate(myObject, myFunction);
I want to do something like this but simpler.
The idea is to have the user click on bolded text and have a text field appear where they can change all the values of a role-playing character. Then when the value is changed, have the text field disappear again replaced by the updated bolded text value.
I can do this already using an annoying text box alert. But I would rather have something similar to this below to replace all that.
I have searched for months and CMS is the closest to answering my question in the simplest way with a full html example. Nobody else on the net could.
So my question is, how do I do this?
I have multiple objects(characters) and need this.elementId to make this work.
I've modified this example but it breaks if I try to add to it.
html>
head>
title>Sandbox
/head>
body>
input id="textarea1" size=10 type=text>
script>
function CharCounter(sourceId, statusId)
{this.sourceId=sourceId;
this.statusId=statusId;
this.count=0;
}
CharCounter.prototype.updateAll=function()
{text=document.getElementById(this.sourceId).value
document.getElementById(this.statusId).innerHTML=text
}
CharCounter.prototype.start=function()
{instance=this
document.getElementById(this.sourceId).onkeyup=function ()
{instance.updateAll()}
}
window.onload=function ()
{myCharCounter=new CharCounter('textarea1', 'status')
myCharCounter.start()
}
/script>
/body>
/html>

Categories