I have an object defined like this:
Blah = {
hideTimer:null,
setTimer: function() {
this.hideTimer = window.setTimeout(Blah.hidePopupInner, 500);
// must be done via window due to Greasemonkey
},
hidePopupInner: function() {
log("This? " + this);
},
hidePopupInnerPublic: function() {
Blah.hidePopupInner();
}
}
The problem is that the 'this' in killTimer is not set to Blah. If I change the line to say
this.hideTimer = window.setTimeout(Blah.hidePopupInnerPublic, 500);
then the 'this' is pointing to Blah so the hideTimer can be utilized.
Making a 'public' method for each method solves the problem, but there must be an easier solution...?
Note: This is all in Greasemonkey, but I think it's a general Javascript question.
To solve this, you can use anonymous function and scope reference when building timeout.
(code...)
setTimer: function() {
var _this = this;
this.hideTimer = window.setTimeout(function(ms){
_this.hidePopupInner();
}, 500);
},
(code...)
PS: Moreover, setTimeout will pass the number of milliseconds to invoked function. For example: imagine your function can receive one parameter, and do some stuff with it. But because setTimeout will pass milliseconds to your function, it can lead to unexpected errors.
Basically function specified as setTimeout param is executed like callback.
Reason you're not getting Blah context is you switching to setTimeout scope (even when using Blah method).
I don't know Greasemonkey at all, however using Function methods like Bind will help you.
If there is no function like bind in GM, you can alwyas write it but yourself (couple of lines of code) - can copy PrototypeJS one.
http://www.prototypejs.org/api/function/bind
It basically executes prepares your function with specifed scope:
// inside Blah
setTimeout (Blah.hidePopupInner.bind(this), 500);
Actually Tableton's solution is Bind's implementation on fly
Though not a true solution to the scope issue, you can at least get around Blah.killTimerPublic by doing:
window.setTimeout(function(){ Blah.hidePopupInner() }, 500);
Related
I am newbie in js and I want to override/overwrite some fullcalendar functions from another script (my-fullcalendar.js) to make some changes in it for myself. for example function names are :
formatRange and oldMomentFormat.
formatRange is accessible from this.$.fullCalendar.formatRange but oldMomentFormat is not accessible via this kind of chain. But even when I do something like this in my-fullcalendar.js:
;(function () {
function MyformatRange(date1, date2, formatStr, separator, isRTL) {
console.log( "MyformatRange");
//other parts is exactly the same
// ...
}
this.$.fullCalendar.formatRange=MyformatRange;
console.log(this);
})();
nothing happens because no log is generated and even line by line tracing does not pass from here. but when observing "this" in console log MyformatRange replaced by original formatRange.
another problem is how can I override/overwrite oldMomentFormat function which is not in window hierarchy to access (or I can not find it) ??
OK, let's simplify the problem. In essence, you have this situation:
var makeFunObject = function () {
var doSomething = function (msg) {
console.log(msg);
};
var haveFun = function () {
doSomething( "fun!");
};
return {
doSomething : doSomething,
haveFun : haveFun
};
};
In other words you have a function that is creating a closure. Inside that closure are two "private" functions, one of which calls the other. But both functions seem to be "exposed" in the returned object.
You write some code:
var myFunObject = makeFunObject();
myFunObject.haveFun(); // fun!
Yep, seems to work just fine. Now let's replace the doSomething function in that returned object and call haveFun again:
myFunObject.doSomething = function (msg) {
console.log("My new function: " + msg);
};
myFunObject.haveFun(); // fun! <== wait what?
But wait! The new replacement function is not being called! That's right: the haveFun function was expressly written to call the internal function. It in fact knows nothing about the exposed function in the object at all.
That's because you cannot replace the internal, private function in this way (you cannot replace it at all, in fact, not without altering the original code).
Now draw back to the FullCalendar code: you are replacing the external function in the object, but the internal function is the one that is called by every other function inside FullCalendar.
I realize this is an old question, but I was butting my head against this same problem when I wanted to override the getEventTimeText function.
I was able to accomplish this, from inside my own JS file, like so:
$.fullCalendar.Grid.mixin({
getEventTimeText: function (range, formatStr, displayEnd) {
//custom version of this function
}
});
So, in terms of the function you were trying to override, you should be able to do it with:
$.fullCalendar.View.mixin({
formatRange: function (range, formatStr, separator) {
//custom formatRange function
}
});
Note: Make sure this runs before where you actually create the calendar. Also note that you need to make sure to override the function in the right place. For example, getEventTimeText was in $.fullCalendar.Grid, while formatRange is in $.fullCalendar.View.
Hopefully this helps other people who end up on this question.
I will show the code directly:
disable: function(e){
that = this;
var haha = this;
$.post(url, function(){
console.log(this);// (why ajax object here?
console.log(that);// (I understand this one works
console.log(haha);// ReferenceError
})
}
What I get confused here is:
Why this in callback does not refer to the outside one? I think this in callback follow the default binding role.
Why haha does not refer as that do? I think when haha is not found in local scope, it will go to outer scope.
I know using that is not a good way. That's why I tried haha, but it failed.
I think you are trying to access those values from console... in that case haha will not work as it is local to the function, where as you have created that as a global variable(as there is no var used).
But that is a wrong pattern because some other script could modify the value of that before the ajax request is completed.
The answer to question 1 is: Because you can rebind it willy-nilly in Javascript, and jQuery happens to for jQuery.post(), as the documentation for jQuery.ajax() states:
The this reference within all callbacks is the object in the context option passed to $.ajax in the settings; if context is not specified, this is a reference to the Ajax settings themselves.
Generally: you should probably never rely on a Javascript library to not rebind this. If you need its value in a nested callback, just save it. Either in a different-named variable, or using Function.bind():
$(function() {
var self = this;
$.post("/echo/json/", (function() {
console.log("self", self); // this will be the document itself
console.log("this", this); // as will self
console.log("self === this", self === this); // should output true
}).bind(this));
});
Example on jsFiddle: https://jsfiddle.net/millimoose/Lx2oxobg/. For what it's worth, I strongly prefer using a separate variable for readability, because you can give it a descriptive name, and the fact that this isn't rebound, and that you've effectively reassigned one of the parameters of the callback, isn't hidden all the way after the block for which this holds true.
As for your question 2, I can't reproduce it, see my second fiddle: https://jsfiddle.net/millimoose/zL352rzf/. As others have stated, you're probably not actually getting the ReferenceError from the console.log() itself, given your screenshot.
Try using context option of $.ajax() to set this of success , error callbacks
disable: function(e) {
$.ajax({
context: this
, type:"POST"
, url:url
, success: function() {
console.log(this);
}
, error: function() {
console.log(this)
}
})
}
The callback function runs in a different its own scope hence this refers to it, not where it was defined. you can use bind or apply or call functions to bind it.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
so this refers to something else, but since that is declared in the scope that the function was declared in it still exists there.
WARNING!! I AM A NOVICE THROUGH AND THROUGH
Alright, so I know there have been a lot questions about Global variables, and I think that's what I'm looking for, but, not exactly. Lately I've been needing to call upon the same lines of code several times. document.getElementById("example").style or similar to little things like that but I need to continuously repeat.
My question is how do I make it so that I make one variable, outside of the function, to save time writing these lines?
What I've been seeing is to simply write it outside like this var inferno = document.getElementById("inferno"); but this is far from working.
This is my code right now, it's simple because I was just using it as a test, but can anyone help me?
var inferno = document.getElementById("inferno");
function infernoClick () {
inferno.style.backgroundColor="red";
}
You have the right idea. Note, though, that the variable doesn't have to be global. It just has to be where all of the code that wants to use it can use it.
For example, this creates a global:
<script>
var inferno = document.getElementById("inferno");
function infernoClick () {
inferno.style.backgroundColor="red";
}
function somethingElse () {
inferno.style.color="green";
}
</script>
(Note that this needs to be after the markup creating the inferno element.)
The problem with globals is that they can conflict with each other, and in fact the global "namespace" is really, really crowded already.
You can avoid that by wrapping up the code that needs inferno in a scoping function, like this:
<script>
(function() {
var inferno = document.getElementById("inferno");
function infernoClick () {
inferno.style.backgroundColor="red";
}
function somethingElse () {
inferno.style.color="green";
}
})();
</script>
That code creates an anonymous function and then calls it immediately, running the code inside.
Now inferno is "global" to the functions that need it, but isn't actually a global.
Let's take a further example:
<script>
(function() {
var outer = 42;
function doSomethingCool() {
var inner = 67;
document.getElementById("someElement").onclick = function() {
alert("inner = " + inner + ", outer = " + outer);
};
}
// Can't use `inner` here, but can use `outer`
alert("outer = " + outer);
doSomethingCool();
})();
</script>
That code wraps everything in a scoping function, and the outer variable is accessible everywhere within that scoping function. It also has a function, doSomethingCool, which has a variable called inner. inner is only accessible within doSomethingCool. Look at what doSomethingCool does: It hooks up an event handler for when someElement is clicked. It doesn't call the function, it just hooks it up.
The really cool thing is that later, when someone clicks the element, that function has access to that inner variable.
And in fact, that's true for arguments you pass into the function as well. One last example:
<input type="button" id="element1" value="One">
<input type="button" id="element2" value="Two">
<script>
(function() {
function hookItUp(id, msg) {
document.getElementById(id).onclick = function() {
alert(msg);
};
}
hookItUp("element1", "This message is for element1");
hookItUp("element2", "And this one is for element2");
})();
</script>
There, we have this function that accepts a couple of arguments, and we call it twice: Once to hook up click on element1, and again to hook up click on element2.
The really cool thing here is that even though the clicks happen much later, after the calls to hookItUp have long-since returned, the functions created when we called hookItUp still have access to the arguments we passed to it — when we click element1, we get "This message is for element1", and when we click element2, we get "And this one is for element2."
These are called closures. You can read more about them on my blog: Closures are not complicated
That'll work, but only if the declaration appears after the point in the DOM where the element actually appears. Try moving your <script> to the very end of the <body>.
Another thing you can do is use the window "load" event to make sure the whole DOM has been seen before your code runs.
for example
var myGlobalVars = {"inferno":null,"othervar":null}; // globals in their own scope
function clickMe(varName,color) { // generic function
myGlobalVars[varName].style.backgroundColor=color;
}
window.onload=function() {
// initialise after the objects are available
for (var o in myGlobalVars) myGlobalVars[o]=document.getElementById(o);
// execute
clickMe("inferno","red");
}
.
.
T.J. Crowder gave a beautiful answer about scoping; just to add on that you can also use an immediately-invoked function expression to create a module with your UI elements, i.e.
var UI = (function() {
...
return {
inferno: document.getElementById("inferno");
};
})();
...
UI.inferno.style = ...;
I'm trying to write a code that adds a class to a div for a limited time, and then removes it.
I tried using javascript's setTimeout, and jQuery's delay, but nothing works.
The element is SET but never REMOVED.
Here's the come I came up with:
window.onload = function() {
$(".button").click(handler);
}
function handler() {
$(this).addClass("onclick");
setTimeout(function() { $(this).removeClass("onclick"); }, 3000); // JS's setTimeout
$(this).addClass("onclick").delay(3000).removeClass("onclick"); // jQuery's delay
}
I don't get what's wrong... I even tried writing a second handler for the setTimeout function.
Thanks in advanced.
The problem you're having is that this is different within the function you're passing to setTimeout than it is outside it. The usual fix is to use the closure by creating a variable to hold it, and using the variable instead:
function handler() {
var $elm = $(this);
$elm.addClass("onclick");
setTimeout(function() {
$elm.removeClass("onclick");
}, 3000);
}
There I've also use the var to cache the result of $(this) because there's no point in doing it more than once.
More background:
In JavaScript, unlike some languages that look similar, this is defined entirely by how a function is called. When you use setTimeout, the way the function gets called will make this be the global object (window, on browsers), so that's why $(this).removeClass(...) wasn't working.
More on this if you're interested:
Mythical methods
You must remember this
this inside the setTimeout call does not refer to the clicked element.
Change it to this:
function handler() {
var t = $(this);
t.addClass("onclick");
setTimeout(function() { t.removeClass("onclick"); }, 3000);
}
Working example - http://jsfiddle.net/5vakN/
Reference for how this works in javascript - http://bonsaiden.github.com/JavaScript-Garden/#function.this
I'm currently having an issue where I have a javascript object that is trying to use setInterval to call a private function inside of itself. However, it can't find the object when I try to call it. I have a feeling that it's because window.setInterval is trying to call into the object from outside but doesn't have a reference to the object. FWIW - I can't get it to work with the function being public either.
The basic requirement is that I may need to have multiple instances of this object to track multiple uploads that are occurring at once. If you have a better design than the current one or can get the current one working then I'm all ears.
The following code is meant to continuously ping a web service to get the status of my file upload:
var FileUploader = function(uploadKey) {
var intervalId;
var UpdateProgress = function() {
$.get('someWebService', {},
function(json) {
alert('success');
});
};
return {
BeginTrackProgress: function() {
intervalId = window.setInterval('UpdateProgress()', 1500);
},
EndTrackProgress: function() {
clearInterval(intervalId);
}
};
};
This is how it is being called:
var fileUploader = new FileUploader('myFileKey');
fileUploader.BeginTrackProgress();
Use this
intervalId = window.setInterval(UpdateProgress, 1500);
setInterval with a literal argument will eval this in the global scope where UpdateProgress is not accessible.
Because it is an eval expression, it does not have access to the scope that setInterval is created in. Try:
intervalId = window.setInterval(UpdateProgress, 1500)
It is generally good practice to avoid eval style expressions wherever possible. For instance, if you wanted to call several functions from the same timer, you would use an anonymous function instead of a string.
window.setInterval(function () {
function1();
function2();
}, 1500)
See also
Why is using javascript eval() a bad idea?
Anonymous function - Wikipedia
+1 to Andy E's head (I can't upvote yet, doh!)
Another gotcha that could get you is if you use this from within the called function.
Then doing exactly what Andy has with this addition should get you by.
var that = this;
window.setInterval(function() {
function1.apply(that);
function2.apply(that);
}, 1500);