Cannot call method ... of undefined - javascript

So I have made this code, it works almost perfectly. The one thing is that setupListener method returns a error at the end, probably it finds a property I don't want to find. I've been struggling with this for a while, but I clearly don't have that much experience with js to solve it. I have setup some console.log methods for debugging. You use this function like this: setupListener(Id or Class or a Tag name as string, event to watch on as string, and an action);
eg. setupListener('.pun', 'click', function (e){ console.log(e); });
var getElement = function (onStr) {
if (onStr.indexOf('.') === 0) {
onStr = onStr.slice(1);
return document.getElementsByClassName(onStr);
}
if (onStr.indexOf('#') === 0) {
onStr = onStr.slice(1);
return document.getElementById(onStr);
}
if (onStr.indexOf('#') !== 0 && onStr.indexOf('.') !== 0) {
return document.onStr = document.getElementsByTagName(onStr);
}
};
var setupListener = function (elementStr, eventStr, action) {
var tempElement = getElement(elementStr);
// element a collection and has addEventListener method
if (tempElement.length > 1 && tempElement[1].addEventListener) {
for (var i = 1; tempElement.length >= i; i++) {
if (typeof(tempElement[i].addEventListener) !== "undefined")
console.log('1'); //debugging
tempElement[i].addEventListener(eventStr, action);
}
}
// IE < 9 Support
// element a collection and has NOT addEventListener method
else if (tempElement.length > 1 && !tempElement[1].addEventListener) {
for (var i = 1; tempElement.length >= i; i++) {
if (typeof(tempElement[i].addEventListener) !== "undefined")
console.log('2'); // debugging
tempElement[i].attachEvent(eventStr, action);
}
}
// element not a collection and HAS addEventListener method
else if (!tempElement.length > 1 && tempElement.addEventListener) {
console.log('3'); // debugging
tempElement.addEventListener(eventStr, action);
}
// element not a collection and has NOT addEventListener method
// IE < 9 support
else if (!tempElement.length > 1 && !tempElement.addEventListener) {
console.log('4'); // debugging
tempElement.attachEvent(eventStr, action);
}
else {
console.log('5'); // debugging
}
};

Your problem is in the part of the collection of elements. Note that you get the first element of the array by array[0] so you get the last element by array[array.length-1]. But what you are doing when you are iterating over the arrays is, starting at 1 and iterating to array.length. When you try to access an unavailable array-element (as array[array.length]) you will get undefined, and thats ecaxtly what your error says. So you have to change the bounds in the for-loops. I.e.
for (var i = 0; i < tempElement.length; i++)

Related

Javascript looping through an array : Cannot read property 'slice' of undefined

My api response looks like this:
id: (...)
user_id: (...)
symptoms: "Sore throat, Headache"
id: (...)
user_id: (...)
symptoms: "Anorexia (Loss of appetite), Shortness of breath (Difficult in breathing), Myalgias (Muscle pains), Sore throat, Headache"
I am trying to match a users symptoms to existing symptoms categories. A user can have upto 14 symptoms. I keep getting an error of Cannot read property 'slice' of undefined when i split and slice the array to get individual symptoms and match them.
When i try to put a default value for the object if a user has less than 14 symptoms, the error persists.
My code:
getSymNum (symp, c) {
var counter = 0
for (var xc in c) {
var symp1 = c[xc].symptoms.split(',')[0]
var symp2 = c[xc].symptoms.split(',')[1].slice(1)
var symp3 = c[xc].symptoms.split(',')[2].slice(2)
var symp4 = c[xc].symptoms.split(',')[3].slice(3)
var symp5 = c[xc].symptoms.split(',')[4].slice(4)
var symp6 = c[xc].symptoms.split(',')[5].slice(5)
if (symp3 !== undefined){
console.log("hello ha")
}
if (symp1 === symp) {
counter++
} else if (symp2 === symp) {
counter++
} else if (symp3 === symp) {
counter++
} else if (symp4 === symp) {
counter++
} else if (symp5 === symp) {
counter++
} else if (symp6 === symp) {
counter++
}
}
return counter
},
You can optimize the check by using array/string methods like contains() or indexOf():
etSymNum (symp, c) {
var counter = 0
for (var xc in c) {
if(c[xc].symptoms.indexOf(symp) !== -1){
counter++;
}
}
return counter
},

for...of statement in js - issue with dom collection

I'm trying to learn ES2015 and i am having issue with the small function that i have to parse some DOM element and to find all textnodes and delete them.
And I have this function in simple for loop statement.
function deleteTextNodes(element) {
if(!element) {
throw new Error ("Element doesn't exist")
}
for (let i = 0; i < element.childNodes.length; i ++) {
if (element.childNodes[i].nodeType == 3) {
element.removeChild(element.childNodes[i]);
i--;
} else if(element.childNodes[i].nodeType == 1) {
deleteTextNodes(element.childNodes[i])
}
}
And my try to rewrite function in for...on statement syntax below
function deleteTextNodes(element) {
if(!element) {
throw new Error ("Element doesn't exist")
}
for( elem of element.childNodes ) {
console.log(elem, elem.nodeType, );
if (elem.nodeType == 3) {
element.removeChild(elem);
} else if(elem.nodeType == 1) {
deleteTextNodes(elem)
}
}
return true
}
Probably, second function works fine except one - for...on jump over next one node after deletes textnode or something like this. I fix this issue in, first function by adding i--;
So, the question is how to fix this issue in second function?
Just an alternative suggestion, since the scope is playing around with ES6:
function deleteTextNodes(element) {
if(!element) {
throw new Error ("Element doesn't exist")
}
let coll = [element];
while(coll.length){
[{nodeType:nt}] = ([element,...coll] = coll);
if (nt == 3) {
element.remove();
} else if(nt == 1) {
coll.push(...element.childNodes);
}
}
}
This prevents the need for recursion by continuously adding the childnodes to the collection. [element,...coll] = coll assigns the first element to the element var and assigns the remaining collection to coll.
Not necessarily better, but it shows some nice elements of destructuring.
function deleteTextNodes(element) {
if (!element) {
throw new Error("Element doesn't exist")
}
let removeElement = [];
for (elem of element.childNodes) {
if (elem.nodeType == 3) {
removeElement.push(elem);
} else if (elem.nodeType == 1) {
deleteTextNodes(elem)
}
}
removeElement.forEach(elem =>elem.remove());
return true
}

Mutable variable is accessible from closure. How can I fix this?

I am using Typeahead by twitter. I am running into this warning from Intellij. This is causing the "window.location.href" for each link to be the last item in my list of items.
How can I fix my code?
Below is my code:
AutoSuggest.prototype.config = function () {
var me = this;
var comp, options;
var gotoUrl = "/{0}/{1}";
var imgurl = '<img src="/icon/{0}.gif"/>';
var target;
for (var i = 0; i < me.targets.length; i++) {
target = me.targets[i];
if ($("#" + target.inputId).length != 0) {
options = {
source: function (query, process) { // where to get the data
process(me.results);
},
// set max results to display
items: 10,
matcher: function (item) { // how to make sure the result select is correct/matching
// we check the query against the ticker then the company name
comp = me.map[item];
var symbol = comp.s.toLowerCase();
return (this.query.trim().toLowerCase() == symbol.substring(0, 1) ||
comp.c.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1);
},
highlighter: function (item) { // how to show the data
comp = me.map[item];
if (typeof comp === 'undefined') {
return "<span>No Match Found.</span>";
}
if (comp.t == 0) {
imgurl = comp.v;
} else if (comp.t == -1) {
imgurl = me.format(imgurl, "empty");
} else {
imgurl = me.format(imgurl, comp.t);
}
return "\n<span id='compVenue'>" + imgurl + "</span>" +
"\n<span id='compSymbol'><b>" + comp.s + "</b></span>" +
"\n<span id='compName'>" + comp.c + "</span>";
},
sorter: function (items) { // sort our results
if (items.length == 0) {
items.push(Object());
}
return items;
},
// the problem starts here when i start using target inside the functions
updater: function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
};
$("#" + target.inputId).typeahead(options);
// lastly, set up the functions for the buttons
$("#" + target.buttonId).click(function () {
window.location.href = me.format(gotoUrl, $("#" + target.inputId).val(), target.destination);
});
}
}
};
With #cdhowie's help, some more code:
i will update the updater and also the href for the click()
updater: (function (inner_target) { // what to do when item is selected
return function (item) {
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}}(target))};
I liked the paragraph Closures Inside Loops from Javascript Garden
It explains three ways of doing it.
The wrong way of using a closure inside a loop
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Solution 1 with anonymous wrapper
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Solution 2 - returning a function from a closure
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
Solution 3, my favorite, where I think I finally understood bind - yaay! bind FTW!
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}
I highly recommend Javascript garden - it showed me this and many more Javascript quirks (and made me like JS even more).
p.s. if your brain didn't melt you haven't had enough Javascript that day.
You need to nest two functions here, creating a new closure that captures the value of the variable (instead of the variable itself) at the moment the closure is created. You can do this using arguments to an immediately-invoked outer function. Replace this expression:
function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
With this:
(function (inner_target) {
return function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}
}(target))
Note that we pass target into the outer function, which becomes the argument inner_target, effectively capturing the value of target at the moment the outer function is called. The outer function returns an inner function, which uses inner_target instead of target, and inner_target will not change.
(Note that you can rename inner_target to target and you will be okay -- the closest target will be used, which would be the function parameter. However, having two variables with the same name in such a tight scope could be very confusing and so I have named them differently in my example so that you can see what's going on.)
In ecmascript 6 we have new opportunities.
The let statement declares a block scope local variable, optionally initializing it to a value.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Since the only scoping that JavaScript has is function scope, you can simply move the closure to an external function, outside of the scope you're in.
Just to clarify on #BogdanRuzhitskiy answer (as I couldn't figure out how to add the code in a comment), the idea with using let is to create a local variable inside the for block:
for(var i = 0; i < 10; i++) {
let captureI = i;
setTimeout(function() {
console.log(captureI);
}, 1000);
}
This will work in pretty much any modern browser except IE11.

TypeError: base.contacts is undefined - how can i define it?

Receiving the above error in reference to the line for ( var i = 0; i < base.contacts.length; i++) { and can't figure out how to resolve. Below is the relevant code section
synckolab.addressbookTools.createTBirdObject = function (base, cards) {
var card = null;
if (base.type === "contact") {
card = Components.classes["#mozilla.org/addressbook/cardproperty;1"].createInstance(Components.interfaces.nsIAbCard);
} else if (base.type === "maillist") {
card = Components.classes["#mozilla.org/addressbook/directoryproperty;1"].createInstance(Components.interfaces.nsIAbDirectory);
card.isMailList = true;
} else {
return null;
}
// for a mailing list add the entries
if (base.type === "maillist") {
card.dirName = this.getCardProperty(base, "DisplayName");
if (this.haveCardProperty(base, "NickName")) {
card.listNickName = this.getCardProperty(base, "NickName");
}
if (this.haveCardProperty(base, "Notes")) {
card.description = this.getCardProperty(base, "Notes");
}
// fill the list
for ( var i = 0; i < base.contacts.length; i++) {
var listCard = cards.get(this.getUID(base.contacts[i]));
card.addressLists.appendElement(listCard, false);
}
} else {
// go through all elements of base
for ( var field in base) {
// skip our own stuff TODO: handle mailing lists!
if (field !== "type" && field !== "synckolab" && field !== "ts" && field !== "contacts" && field !== "isMailList") {
// copy the property from base to card
this.setCardProperty(card, field, this.getCardProperty(base, field));
}
}
}
return card;
};
base is a parameter you hand over to the function which is supposed to be an object having (at least) the following properties:
type
contact
Obviously it is missing the contact property, which again should be an array. So you should take a look at the object which you are handing over to the function.
To fix this problem temporarily you could do:
if (base.contacts){
for ( var i = 0; i < base.contacts.length; i++) {
/*...*/
}
}
However to really fix the problem, you should take a look at the code, where the function is called and examine, how the object which you hand to the function is constructed. Do a
console.log(base);
to take a closer look at the object.

Passing argument to anonymous function

want to pass a simple integer to function after finding it. Code is as below:
$("#play").click(function() {
var elementNumber;
var properElement;
for (var i = 0; i < playlistLength; i++) {
if (listOfElements[i].type == 'video' && listOfElements[i].position == 0) {
elementNumber = i;
}
};
document.getElementById(listOfElements[elementNumber].name).play();
properElement = listOfElements[elementNumber];
console.log(properElement); // gives out proper element;
setInterval("galleryElement.draw(properElement.name, properElement.type, properElement.position)", 33); // Cannot read property 'name' of undefined
return false;
})​
and I get an error Cannot read property "name" of undefined? How can I pass arguments there?
Thanks in advance!
$("#play").click(function() {
var elementNumber;
var properElement;
for (var i = 0; i < playlistLength; i++) {
if (listOfElements[i].type == 'video' && listOfElements[i].position == 0) {
elementNumber = i;
}
};
document.getElementById(listOfElements[elementNumber].name).play();
properElement = listOfElements[elementNumber];
console.log(properElement); // gives out proper element;
setInterval(function() {
galleryElement.draw(properElement.name, properElement.type, properElement.position)
}, 33);
return false;
})
or (won't work in IE)
$("#play").click(function() {
var elementNumber;
var properElement;
for (var i = 0; i < playlistLength; i++) {
if (listOfElements[i].type == 'video' && listOfElements[i].position == 0) {
elementNumber = i;
}
};
document.getElementById(listOfElements[elementNumber].name).play();
properElement = listOfElements[elementNumber];
console.log(properElement); // gives out proper element;
setInterval(galleryElement.draw, 33, properElement.name, properElement.type, properElement.position);
return false;
})
try
setInterval(function(){
galleryElement.draw(properElement.name, properElement.type, properElement.position)
}, 33);
You need to put properElement in the scope of the function to be executed.
If you pass a string to setInterval() you will lose scope of your properElement. You have to pass the functioncall directly (without quotes):
setInterval(function(){
galleryElement.draw(properElement.name, properElement.type, properElement.position)
}, 33);
In general it is not recommended to pass a string to setInterval/Timeout because it needs to be evaluated via eval. Always pass a function or a reference to a function.

Categories