why is the console giving me an exception, when startsWith is defined? - javascript

ok i am trying to run the following code and i am getting back an exception that startsWith method is undefined. shouldnt this automatically be defined within javascript?
var mailArchive = retrieveMails();
var livingCats = {"Spot": true};
for (var mail = 0; mail < mailArchive.length; mail++) {
var paragraphs = mailArchive[mail].split("\n");
for (var paragraph = 0;
paragraph < paragraphs.length;
paragraph++) {
if (startsWith(paragraphs[paragraph], "born")) {
var names = catNames(paragraphs[paragraph]);
for (var name = 0; name < names.length; name++)
livingCats[names[name]] = true;
}
else if (startsWith(paragraphs[paragraph], "died")) {
var names = catNames(paragraphs[paragraph]);
for (var name = 0; name < names.length; name++)
delete livingCats[names[name]];
}
}
}
show(livingCats);

Some versions of javascript (an experimental method in Firefox) have a .startsWith() string method that you would use like:
if (paragraphs[paragraph].startsWith("died"))
Regular javascript does not have a global function startsWith() like you are trying to use so unless you're loading some library that has that function in it, that's why the browser is telling you it's undefined.
A recommended solution that would work in any version of JS, would be:
if (paragraphs[paragraph].indexOf("died") === 0)
instead.
Or, you could define the global function you're using:
function startsWith(src, find) {
return src.substr(0, find.length) == find;
}

It's a method of String, not a global function.
It should be:
paragraphs[paragraph].startsWith("born")

Related

How to pass an object's method as a parameter to another function in Javascript

First take a look at my simple codes below:
function mySecondFunction(objArray,setFunc)
{
for (let i = 0; i < objArray.length; i++)
{
objArray[i].info.setTop(72);
}
}
function myFunction()
{
let myObjArray = [];
for (let i = 0; i < 10; i++)
{
myObjArray.push({
info:{topVar:0,
bottomVar:0,
get top() {return this.topVar;},
get bottom() {return this.bottomVar;},
setTop: function(input) {this.topVar = input;},
setBottom: function(input) {this.bottomVar = input; }
}
});
}
mySecondFunction(myObjArray); // This works Fine
mySecondFunction(myObjArray,setTop); // I want something like this!!!
}
As you can see, I want to pass a method of an object to another function. I know a lot of possible solutions to avoid this, but I want to know whether it is possible or not.
Detach it and pass as an argument. Remember to use call to set the intended this value.
function mySecondFunction(objArray, setFunc)
{
for (let i = 0; i < objArray.length; i++)
{
setFunc.call(objArray[i].info, 72);
/* explicitly telling that:
please set 'this' value in this function to be 'objArray[i].info' when running,
allowing, e.g. `this.topVar` in
`setTop: function(input) {this.topVar = input;}`
to be operating on `objArray[i].info.topVar` */
}
}
function myFunction()
{
let myObjArray = [];
for (let i = 0; i < 10; i++)
{
myObjArray.push({
info:{topVar:0,
bottomVar:0,
get top() {return this.topVar;},
get bottom() {return this.bottomVar;},
setTop: function(input) {this.topVar = input;},
setBottom: function(input) {this.bottomVar = input; }
}
});
}
mySecondFunction(myObjArray, myObjArray[0].info.setTop);
/* once detaching the method from the object,
(if we are not using arrow functions),
we lose 'this' value, meaning we are losing
the target of object that we want to operate on */
console.log(myObjArray)
}
myFunction();
You can target item number in the array list. You can do statically (i.e. 1-???) or dynamically with an iteration and a variable. You can then the object property within that. For example:
myObjArray[0].info.setTop
That will target the 1st item in the array. Be sure to omit parentheses (()) when passing the method as you want to pass the function reference not the result

Use closure inside array with pure javascript

I need to copy a string inside an array to a value inside another array that is created in a loop. In the end when I print all names are the last in the array of names. I want to copy/clone the value so that I don't have a reference and I would like it to be only in native javascript without external libraries.
This is my code
var exp_names =["name1","name2","name3"];
var i;
for (i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
});
And then all expId are "name3"
Data is loading correctly per file.
I have tried with jquery's extend function and also lodash's clone function, I have tried my own clone function and nothing works it will still throw "name3" for all the expId.
These didn't work:
var newname = new String(exp_names[i]);
var newname = $.extend(true, {}, exp_names[i]);
var newname = $.extend( {}, exp_names[i]);
var newname = _.clone(exp_names[i]);
var newname = exp_names[i].slice(0);
I am desperate by now.
You need to use bind function.
var exp_names =["name1","name2","name3"];
var i;
var func = [];
for (i = 0; i < exp_names.length; i++) {
func[i]=(function(index){
d3.tsv("data/"+exp_names[index], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[index];
});
});
}).bind(this,i);
}
for(i = 0; i < 3; i++){
func[i](i);
}
Another solution is to use let keyword.
ES6 provides the let keyword for this exact circumstance. Instead of using closures, we can just use let to set a loop scope variable.
Please try this:
for (let i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
}
I guess usage of IIFE and bind together, in the first answer is a little weird. It's best to choose either one of them. Since in the newest versions of the browsers bind is way faster than an IIFE closure and the let keyword I might suggest you the bind way.
A similar example to your case might be as folows;
var exp_names = ["name1","name2","name3"],
lib = {doStg: function(d,cb){
cb(d);
}
},
data = [{a:1},{a:2},{a:3}];
for (i = 0; i < exp_names.length; i++) {
lib.doStg(data, function(i,d) {
d.forEach(function(e){
//Do stuff with doStg
e.expId = exp_names[i];
console.log(e);
});
}.bind(null,i));
}

Get object out of observable array

Why is m "undefined" in this code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for(var i=0;i<currentViewModel.availableReports().length;i++) {
if(currentViewModel.availableReports()[i].id == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
I call getReport() as an onclick event and I want to send the report object to a view (modal) I can do a foreach on the availableReports and it's all there. When I run through the debugger, it loops through the array and finds the right one. But why can't I pull it out of the array? "m" remains undefined the the function returns undefined.
What am I missing here?
EDIT: there is a follow up question here:
Can knockout.js wait to bind until an onClick?
You just need to change if(currentViewModel.availableReports()[i].id ... to if(currentViewModel.availableReports()[i].id() ... because after mapping id will become an observable, i.e. function.
Updated code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id() == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
Demo - Fiddle.
I'll repeat the solution from #NikolayErmakov's answer here, but want to add two things to get a more complete answer. You end with:
...m remains undefined and the function returns undefined.
What am I missing here?
You're missing two things:
The var m bit of the first statement inside the if is hoisted to the top of the current scope (the top of the function). This is why the debugger can tell you what m is, even if you never reach the line of code it's on.
If a function invocation reaches the end of a function (as is the case for you, since you never go inside the if) without seeing an explicit return statement, it will return undefined.
To better understand this, you should interpret your function like this:
currentViewModel.getReport = function(reportId) {
var m;
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id == reportId) {
m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
return undefined;
}
Some people (e.g. Douglas Crockford) do recommend placing var statements at the top of a function, though it's a matter of style to some degree. I don't think many people explicitly return undefined at the end of a function, though in your case I might be explicit about that scenario and return null (or throw an Error even).
As promised, I'll repeat the actual solution, as I concur with the other answer:
you need to invoke id as a function to get its value (because the mapping plugin will map to observable()s.
In addition:
I'd retrieve the array only once
I'd suggest using === instead of ==
Here's my v0.5 version:
currentViewModel.getReport = function(reportId) {
var m = null, reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
m = reports[i];
return m;
}
}
return m;
}
But I'd optimize it to this v1.0:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
return reports[i];
}
}
return null;
}
For completeness, here's another version that utilizes filter on arrays:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports().filter(function(r) { return r.id() === reportId; });
return reports.length >= 1 ? reports[0] : null;
}

Is it possible to use an undeclared variable in another variable

I have always wondered this and every now and again it pops up.
var name = document.title;
var user = document.getElementsByClassName("class-name")[0].children[j].getElementsByTagName("a")[0].innerHTML;
var someArr = [];
for (var j = 0; j < document.getElementsByClassName("class-name")[0].children.length; j++) {
if (user == name) {
someArr.push(user)
};
};
alert(someArr);
Now this is all made up (obviously) but see how the variable "user" is checking for children[j], well if I were to try use this code, it would come up with an error along the lines of "Cannot read property 'getElementsByTagName' of undefined". Now my question is: Is there a way to allow this code to work without giving an error message and not executing. I would use this for clean code in the if loop, like all variables are used for. This wouldn't be the only instance of the "user" variable being used either so it is very useful.
Thanks in advance,
Daniel.
Your j variable will have undefined value if you use it before the for loop.
You could put it inside a function closure, but using variables like this does not make code readable in my opinion. What you can do is create a getUser() function and also cache the array of elements. Something like this:
var name = document.title;
var getUser = function(elem) {
return elem.getElementsByTagName('a')[0].innerHTML;
};
var initialArr = document.getElementsByClassName("class-name")[0].children;
var someArr = [];
for (var j = 0; j < initialArr.length; j++) {
var user = getUser(initialArr[j]);
if (user == name) {
someArr.push(user)
};
};
alert(someArr);

OO JS - loop through object array

Quite new to OO Js, used to program with function after function so trying to fix that now!
I'm making a tab layout -
I create a tab by calling: tab.NewTab();
I can access the tabs at tab[0], tab[1] etc
var tabCount = 0;
var tabs = [];
tabs.NewTab = function (){
var tabName = "tab" + tabCount;
tabs[tabCount] = new Tab(tabName);
tabCount++;
};
function Tab(tabName){
return{
name: tabName
}
}
I wanted to make a function that counts how many tabs are open:
tabs.HowMany = function () {
for (var i in tabs) {
alert("new");
}
};
This is returning the methods too (0,1, NewTab, HowMany).
Any advice?
You're looking for tabs.push(new Tab(tabName));
Then ditch tabCount, and instead use the length property native to all javascript arrays: tabs.length
Also, your Tab constructor is wrong. As it is currently written it should not be called with new. Just call Tab('someName') and it will return to you the object you're looking for. If you do that however, change it to tab since non-constructor functions should be lowercase.
If you're really eager to use the new keyword, this is what Tab should look like:
function Tab(tabName){
this.name = tabName;
}
EDIT
If you want to iterate over all members of your array, this is the simplest way:
for (var i = 0; i < tabs.length; i++)
var currentTab = tabs[i];
You need to change var tabs = []; to var tabs = new Array();.
And add items to it using tabs.push(new Tab(tabName));
Simple count:
var count = tabs.lenght
Enumeration of items:
for (var i = 0; i < tabs.length; i++) {
alert(tabs[i]);
}
You could do:
tabs.HowMany = function () {
for (var i in tabs) {
if(tabs.hasOwnProperty(i)){
if(typeof tabs[i] !== 'function'){
alert('new')
}
}
}
};
but you'd better switch using it as array
tabs.push(new Tab(tabName))
tabs.HowMany = function () {
return tabs.length
};

Categories

Resources