I have 3 methods
exports.getImageById = function (resultFn, id) {
...
}
exports.getCollectionById = function (resultFn, id) {
}
in the third method I want to call both methods
exports.getCollectionImages = function (resultFn, collectionId) {
var arr = new Array();
this.getCollectionById( // fine, 1st call
function (result) {
var images = result.image;
for (i = 0; i < images.length; i++) {
this.getImageById(function (result1) { // error, 2nd call
arr[i] = result1;
}, images[i]
);
}
}
, collectionId
);
resultFn(arr);
}
I can call first function this.getCollectionById but it fails to call this.getImageById, it says undefined function, whats the reason for that?
When you call this.getCollectionById passing it a callback, the callback doesn't have access to the same this
The simplest solution is to save this as a local variable.
exports.getCollectionImages = function (resultFn, collectionId) {
var arr = new Array();
var me = this; // Save this
this.getCollectionById( // fine, 1st call
function (result) {
var images = result.image;
for (var i = 0; i < images.length; i++) {
// Use me instead of this
me.getImageById(function (result1) { // error, 2nd call
arr[i] = result1;
}, images[i]);
}
}, collectionId);
resultFn(arr);
}
The value of this inside the inner function is not the same object as outside, because it's determined depending on how the function is called. You can find a detailed explanation in the MDN article on this.
One of the ways to solve it is by keeping a reference to the outer this in another variable such as that:
var that = this;
this.getCollectionById( // fine, 1st call
function (result) {
var images = result.image;
for (i = 0; i < images.length; i++) {
that.getImageById(function (result1) { // 2nd call
arr[i] = result1;
}, images[i]
);
}
}
, collectionId
);
Related
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'm try to make something and i made this piece of code,but when i press the botton it's happend for a sec and then disappear,am i donig passing the arguments wrong or something?
here the code is:
{
var fil1;
var rtextDiv;
for (var i = 0; i < dmsg.getElementsByClassName('refilter').length; i++) {
var refilterInput = dmsg.getElementsByClassName('refilter')[i];
refilterInput.addEventListener('keyup', firstfilter(rtextDiv, fil1,refilterInput));
}
};
function firstfilter(e, rtextDiv, fil1, refilterInput) {
rtextDiv = refilterInput.parentNode.parentNode.getElementsByClassName('rtext')[0];
while (rtextDiv.firstChild) {
rtextDiv.removeChild(rtextDiv.firstChild);
}
fil1 = filteredPropertiesTable(res, refilterInput.value);
rtextDiv.appendChild(fil1);
};
edited as the comment said:
{
var fil1;
var rtextDiv;
for (var i = 0; i < dmsg.getElementsByClassName('refilter').length; i++) {
var refilterInput = dmsg.getElementsByClassName('refilter')[i];
refilterInput.addEventListener('keyup', function()
{firstfilter(rtextDiv,fil1,refilterInput)(rtextDiv, fil1,refilterInput)});
);
}
};
function firstfilter(e, rtextDiv, fil1, refilterInput) {
rtextDiv = refilterInput.parentNode.parentNode.getElementsByClassName('rtext')[0];
while (rtextDiv.firstChild) {
rtextDiv.removeChild(rtextDiv.firstChild);
}
fil1 = filteredPropertiesTable(res, refilterInput.value);
rtextDiv.appendChild(fil1);
};
is it true know?can i pass argument that way?
Here you are actually executing the handler:
refilterInput.addEventListener('keyup', firstfilter(rtextDiv, fil1,refilterInput));
You should just present the handler name:
refilterInput.addEventListener('keyup', firstfilter);
And the handler can be improved:
function firstfilter(e) {
var rtextDiv = this.parentNode.parentNode.getElementsByClassName('rtext')[0];
while (rtextDiv.firstChild) {
rtextDiv.removeChild(rtextDiv.firstChild);
}
var fil1 = filteredPropertiesTable(res, this.value); // you didn't say what is res
rtextDiv.appendChild(fil1);
};
I'm writing a piece of code to easily save error logs in an object for debugging.
What I'm trying to achieve is to get the Object name from the function it was called from like so:
var MainObject = {
test : function() {
return MainObject.test.caller;
// When called from MainObject.testcaller,
// it should return MainObject.testcaller.
},
testcaller : function() {
return MainObject.test(); // Should return MainObject.testcaller, Returns own function code.
},
anothercaller : function() {
return MainObject.test(); // Should return MainObject.anothercaller, Returns own function code.
}
}
However when I run this code it returns the function code from MainObject.testcaller.
JSFiddle example
Is there any way this is possible?
Update
After looking at Rhumborl's answer, I discovered that assigning the value through another function would lead it to point back at the function name without the object itself.
Code:
(function (name, func) {
MainObject[name] = func;
})('invalid', function() {
return MainObject.test("blah");
});
// This now points at invalid() rather than MainObject.invalid()
Updated fiddle
There is a non–standard caller property of functions that returns the caller function, however that is a pointer to a function object and doesn't tell you the object it was called as a method of, or the object's name. You can get a reference to the function through arguments.callee.
There is also the obsolete arguments.caller, but don't use that. It also provides a reference to the calling function (where supported).
Once you have a reference to the calling function (if there is one), you then have the issue of resolving its name. Given that Functions are Objects, and objects can be referenced by multiple properties and variables, the concept of a function having a particular name is alluvial.
However, if you know that the function is a property of some object, you can iterate over the object's own enumerable properties to find out which one it is.
But that seems to be a rather odd thing to do. What are you actually trying to do? You may be trying to solve a problem that can be worked around in a much more robust and simpler way.
Edit
You can do what you want in a very limited way using the method described above for the case in the OP, however it is not robust or a general solution:
var mainObject = {
test : function() {
var obj = this;
var caller = arguments.callee.caller;
var global = (function(){return this}());
var fnName, objName;
for (var p in global) {
if (global[p] === obj) {
objName = p;
}
}
for (var f in obj) {
if (obj[f] === caller) {
fnName = f;
}
}
return objName + '.' + fnName;
},
testcaller : function() {
return mainObject.test();
},
anothercaller : function() {
return mainObject.test();
}
}
console.log(mainObject.testcaller()); // mainObject.testcaller
console.log(mainObject.anothercaller()); // mainObject.anothercaller
but it's brittle:
var a = mainObject.anothercaller;
console.log(a()); // mainObject.anothercaller
var b = {
foo : mainObject.anothercaller
}
console.log(b.foo()); // mainObject.anothercaller
Oops.
You can use this trick at http://www.eriwen.com/javascript/js-stack-trace/ which throws an error, then parses the stack trace.
I have updated it for the latest versions of Firefox, Chrome and IE. Unfortunately it doesn't work well on my IE9 (and I haven't tested it on Opera).
function getStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist += 0; //doesn't exist- that's the point
} catch (e) {
if (e.stack) { //Firefox/Chrome/IE11
var lines = e.stack.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
var line = lines[i].trim();
if (line.match(/^at [A-Za-z0-9\.\-_\$]+\s*\(/)) {
// Chrome/IE: " at Object.MainObject.testcaller (url:line:char)"
var entry = line.substring(3, line.indexOf('(') - 1);
// Chrome appends "Object." to the front of the object functions, so strip it off
if (entry.indexOf("Object.") == 0) {
entry = entry.substr(7, entry.length);
}
callstack.push(entry);
} else if (line.match(/^[A-Za-z0-9\.\-_\$]+\s*#/)) {
// Firefox: "MainObject.testcaller#url:line:char"
callstack.push(line.substring(0, lines[i].indexOf('#')));
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
} else if (window.opera && e.message) { //Opera
var lines = e.message.split('\n');
for (var i = 0, len = lines.length; i < len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i + 1]) {
entry += lines[i + 1];
i++;
}
callstack.push(entry);
}
}
//Remove call to getStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE9 and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf('')) || 'anonymous';
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
return callstack;
}
var MainObject = {
test: function (x) {
// first entry is the current function (test), second entry is the caller
var stackTrace = getStackTrace();
var caller = stackTrace[1];
return caller + "()";
},
testcaller: function () {
return MainObject.test(1, null);
}
}
function SomeFunction() {
return MainObject.test("blah");
}
document.body.innerHTML += '<b style="color: red">' + MainObject.testcaller() + '</b>';
document.body.innerHTML += '<div>Calling SomeFunction() returns: <b style="color: red">' + SomeFunction() + '</b></div>';
MainObject.test() should return: <b style="color: blue">MainObject.testcaller()</b>
<hr />
MainObject.test() returns:
Updated fiddle here
I have this code. It does that always when there is function, that is interested on some key in Session, it will be called whenever the key changed its value.
The problem is that to track what keys the function is interested, i need to run that function once and it can have some sideffects (if that function will manipulate DOM for instance). How can i run that function without affecting the current environment, if it is possible....?
var checkRunning = false;
var keys = [];
var checks = {};
var Session = {
get: function (key) {
if (checkRunning) {
keys.push(key);
}
},
set: function (key, value) {
if (checks[key]) {
var l = checks[key].lenght;
for (var i = 0; i < l; i++) {
checks[key][i]();
}
}
}
};
function check(f) {
checkRunning = true;
f();
checkRunning = false;
var l = keys.lenght;
for (var i = 0; i < l; i++) {
if (checks[keys[i]]) {
checks[keys[i]].push(f);
}
else {
checks[keys[i]] = [f];
}
}
keys = [];
}
//how to use
var a = "something";
check(function () {
// this function should be run always when Session key "a_dep" will change
a = Session.get("a_dep");
});
Session.set("a_dep", 10);
Session.set("a_dep", 20);
So I refactored your code to achieve what I understood you wanted.
I first redefined Session and its getter/setter. I store the values in a literal object with the key to access it :
function Session () {
this.myDictionnary = {};
this.myCallbacks = {};
}
Session.prototype.set = function (key, value) {
this.myDictionnary[key] = value;
}
Session.prototype.get = function (key) {
return this.myDictionnary[key];
}
Here we have our core code. Now you want to call a function every time you set a value to a key, so we have to
Set a function (or function set) - key couple
Call this function (or these functions) when a value is set to key
Note the myCallbacks literal object which will handle these couples. So first, create the key - function couple :
Session.prototype.callbackWhenSet = function (key, callback) {
if(!this.myCallbacks[key]) {
this.myCallbacks[key] = [];
}
this.myCallbacks[key].push(callback);
}
Then call it when set is called (here I rewrite the set function )
Session.prototype.set = function (key, value) {
this.myDictionnary[key] = value;
if(this.myCallbacks[key]) {
for(var i = 0; i < this.myCallbacks[key].length; i++) {
this.myCallbacks[key][i]();
}
}
}
Finally we can test it !
var test = new Session();
test.callbackWhenSet("a_dep", function () {
alert("a_dep is set to " + test.get("a_dep"));
});
test.set("a_dep", 10);
test.set("something", 250);
test.set("a_dep", 20);
As you can see here, we have an alert showed each time "a_dep" is set.
Or you can use ProAct.js and do stuff like this:
var obj = ProAct.prob({
a: 4
});
obj.p('a').on(function () {
console.log('obj.a has changed!');
});
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();