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.
Related
I have a SCOPE problem. When I declare "var text" outside the function all works. But inside the function it works only in the first part. Here is what I mean:
This is a buffer function. Executing buffer("anything") saves "anything". Executing buffer() - without the properties will return all properties.
buffer("Al")
buffer("ex")
buffer() <= should return Alex
But the SCOPE of "text" is wrong and it does not return the saved properties.
function makeBuffer() {
var text = "";
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
var buffer = makeBuffer;
buffer("One", "Two");
document.write(buffer());
That is normal behaviour.
A variable defined in a given scope goes away when the scope goes away. Each call the to the function creates a new scope.
Declaring the variable outside the function is the standard way to share a value between invocations of it.
What you want is a factory:
function makeBuffer() {
var text = "";
return function buffer() {
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
}
var buffer = makeBuffer();
buffer("One", "Two");
document.write(buffer());
You could do this using an object. This will make your code much more organized.
var Buffer = function() {
this.text = "";
}
Buffer.prototype.append = function() {
for (var i = 0; i < arguments.length; i++) {
this.text += arguments[i];
}
}
Buffer.prototype.get = function() {
return this.text;
}
var buffer = new Buffer();
buffer.append("One", "Two");
document.write(buffer.get());
Using ES6 the syntax gets even sweeter:
class Buffer {
constructor() {
this.text = "";
}
append() {
this.text = this.text.concat(...arguments);
}
get() {
return this.text;
}
}
var buffer = new Buffer();
buffer.append("One", "Two");
document.write(buffer.get());
As Quentin properly pointed out in his answer, that is a normal behavior.
An alternative option to keep the value on your function without declaring the variable outside is scope is to add it as a property to the function itself.
As in JavaScript a function is a first class object, you can put such data directly into function object (like in any other objects).
An example below, please note how to get property text from your function (buffer.text).
function makeBuffer() {
makeBuffer.text = "";
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
makeBuffer.text += arguments[i];
console.log(`New text - (${makeBuffer.text})`);
}
} else {
console.log(`text - (${makeBuffer.text})`);
return makeBuffer.text;
}
}
var buffer = makeBuffer;
buffer("Al", "ex");
console.log(`buffer.text - (${buffer.text})`);
Alternatively consider using a closure in order to keep the value of text between function calls.
Closures are functions that refer to independent (free) variables
(variables that are used locally, but defined in an enclosing scope).
In other words, these functions 'remember' the environment in which
they were created. More info here.
let makeBuffer = function() {
// closure
let text = "";
return function() {
if (arguments.length != 0) {
for (let i = 0; i < arguments.length; i++) {
console.log(`Adding argument - (${arguments[i]})`);
text += arguments[i];
console.log(`New text - (${text})`);
}
} else {
console.log(`text - (${text})`);
return text;
}
}
};
var buffer = makeBuffer();
buffer("Al", "ex");
Given the following obj:
var inputMapping = {
nonNestedItem: "someItem here",
sections: {
general: "Some general section information"
}
};
I'm writing a function to get that data by passing in a string "nonNestedItem" or in the nested case "sections.general". I'm having to use an eval and I was wondering if there was maybe a better way to do this.
Here is what I have so far and it works okay. But improve!
function getNode(name) {
var n = name.split(".");
if (n.length === 1) {
n = name[0];
} else {
var isValid = true,
evalStr = 'inputMapping';
for (var i=0;i<n.length;i++) {
evalStr += '["'+ n[i] +'"]';
if (eval(evalStr) === undefined) {
isValid = false;
break;
}
}
if (isValid) {
// Do something like return the value
}
}
}
Linky to Jsbin
You can use Array.prototype.reduce function like this
var accessString = "sections.general";
console.log(accessString.split(".").reduce(function(previous, current) {
return previous[current];
}, inputMapping));
Output
Some general section information
If your environment doesn't support reduce, you can use this recursive version
function getNestedItem(currentObject, listOfKeys) {
if (listOfKeys.length === 0 || !currentObject) {
return currentObject;
}
return getNestedItem(currentObject[listOfKeys[0]], listOfKeys.slice(1));
}
console.log(getNestedItem(inputMapping, "sections.general".split(".")));
You don't need to use eval() here. You can just use [] to get values from an object. Use a temp object to hold the current value, then update it each time you need the next key.
function getNode(mapping, name) {
var n = name.split(".");
if (n.length === 1) {
return mapping[name];
} else {
var tmp = mapping;
for (var i = 0; i < n.length; i++) {
tmp = tmp[n[i]];
}
return tmp;
}
}
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.
First the following is the code of my own javascript library.
(function() {
var lib = {
elems: [],
getElem: function() {
var tmpElem = [];
for (var i = 0; i < arguments.length; i++)
tmpElem.push(document.getElementById(arguments[i]));
this.elems = tmpElem;
tmpElem = null;
return this;
},
html: function(txt) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].innerHTML = txt;
return this;
},
style: function(prob, val) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].style[prob] = val;
return this;
},
addEvent: function(event, callback) {
if (this.elems[0].addEventListener) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].addEventListener(event, callback, false);
} else if (this.elems[0].attachEvent) {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].attachEvent('on' + event, callback);
}
return this;
},
toggle: function() {
for (var i = 0; i < this.elems.length; i++)
this.elems[i].style.display = (this.elems[i].style.display === 'none' || '') ? 'block' : 'none';
return this;
},
domLoad: function(callback) {
var isLoaded = false;
var checkLoaded = setInterval(function() {
if (document.body && document.getElementById)
isLoaded = true;
}, 10);
var Loaded = setInterval(function() {
if (isLoaded) {
clearInterval(checkLoaded);
clearInterval(Loaded);
callback();
}
}, 10);
}
};
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
window.lib = window.$ = fn;
})();
Previously, I have used this way to use my own library, and works fine .
$.getElem('box').html('Welcome to my computer.');
But when updated the code of my own library, and I added
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
To be using the element selector like this way
$('box').html('Welcome to my computer.');
But the problem began appear when added the updated code to clone the lib object TypeError: $(...).html is not a function.
And now I want to use the element selector like that
$('box').html('Welcome to my computer.');
instead of
$.getElem('box').html('Welcome to my computer.');
You create a variable fn which has a reference to "getElem" but since fn is not a property on your lib object then it means that when getElem refers to "this" it will be you global object which is propbably window.
Remove all the following 3 lines
var fn = lib.getElem;
for(var i in lib)
fn[i] = lib[i];
and then do this
window.$ = function () { return lib.getElem.apply(lib, arguments); };
This will allow getElem to be called as $ but maintaining "lib" as context.
Although I don't know exactly what you are trying to achieve with those additional lines, just by reading the code, lib.getElem does not have a function called html
lib does.
Hence, just var fn = lib; should do just fine.
There more ways to achieve this but the root cause is in your getElem() function: return this;
$ is a reference to that function. If you call $() it is called as a function and not as a method. Therefore this refers to window and window has, of course, no html() function.
You could do return lib; to fix the problem.
I'm playing with JS a bit and have following code snippet
var Dog = function(name) {
this.name = name
}
Dog.prototype= {
'bark': function() {
alert(this.name + ' is barking');
},
'run': function() {
alert(this.name + ' is running');
}
}
var dogs = [new Dog('first'), new Dog('second'), new Dog('third')];
function invokeOnDog(what) {
if(what === 'bark') {
for(var i=0; i<dogs.length; i++) {
dogs[i].bark();
}
}
if(what === 'run') {
for(var i=0; i<dogs.length; i++) {
dogs[i].run();
}
}
}
What I'd like to do is to simplify this invokeOnDog function cause it repeats the same template twice. I'm thinking about somehow returning method that should be invoked on object but have no idea how to do that.
Could you help me with that?
EDIT:
Thanks for quick responses. They are ok if "what" has the same name as method to invoke. But what if there is no match between those two?
invokeOnDog('aggresive') should invoke bark method and invokeOnDog('scared') should invoke run
You can access a object property (in this case the 'bark' and the 'run' method) from a string if instead of
object.property
You use
object['property']
And if you have "property" in a variable you can do
var thing = 'property';
object[thing];
Since you have a variable with the name of the method you want to call you can call the method with:
dogs[i][what]();
So it will be like this:
function invokeOnDog(what) {
if (what === 'bark' || what === 'run') {
for(var i=0; i<dogs.length; i++) {
dogs[i][what]();
}
}
}
Update:
If the variable has no relation with the method you want to call you can use a mapping to set the relations:
function invokeOnDog(position) {
var methods = {
'agressive': 'bark',
'defensive': 'run'
};
var method = methods[position];
if (method)
for(var i=0; i<dogs.length; i++) {
dogs[i][method]();
}
}
}
This is the simplest code, but I recommend you to check if "position" value is a key on "methods" and not an inherited method:
function invokeOnDog(position) {
var methods = {
'agressive': 'bark',
'defensive': 'run'
};
if (mehtods.hasOwnProperty(position) {
var method = methods[position];
for(var i=0; i<dogs.length; i++) {
dogs[i][method]();
}
}
}
Otherwise "invokeOnDog('toString')" will access "methods['toString']" who is a function.
Use the bracket notation for accessing the properties which hold the functions:
function invokeOnDog(what) {
for (var i=0; i<dogs.length; i++)
dogs[i][what]();
}
Maybe you want to add a check for the existance of the method on dogs, you can use what in dogs[i] or typeof dogs[i][what] == "function" for that.
You should also check if the property exists before invoking:
function invokeOnDog(what) {
if (Dog.prototype.hasOwnProperty(what)) {
for (i = 0, len = dogs.length; i < len; i++) {
dogs[i][what]();
}
}
}
The Javascript array syntax can also be used to access fields of object. So dog.bark() can be substituted with dog["bark"]().
function invokeOnDog(what) {
for(var i=0; i<dogs.length; i++) {
dogs[i][what]();
}
}
function invokeOnDog(what) {
for(var i=0; i<dogs.length; i++) {
dogs[i][what]()
}
}
try this, i think it should work
You can access the method with a normal attribute lookup.
function invokeOnDog(what) {
if(what === 'bark' || what === 'run') {
for(var i=0; i<dogs.length; i++) {
dogs[i][what]();
}
}
}
Like this?
function invokeOnDog(a) {
if (a === "bark") for (var b = 0; dogs.length > b; b++) dogs[b].bark();
if (a === "run") for (var b = 0; dogs.length > b; b++) dogs[b].run()
}
var Dog = function (a) {
this.name = a
};
Dog.prototype = {
bark: function () {
alert(this.name + " is barking")
},
run: function () {
alert(this.name + " is running")
}
};
var dogs = [new Dog("first"), new Dog("second"), new Dog("third")]