I have a question regarding contexts in JavaScript which I find confusing (probably since I'm new to JavaScript/Web dev overall). I am invoking the function initialize and specifying this as the context to run it in, within it I'm subscribing to keyup event from the input element and binds it to this context. However the function search is invoked in window function even though it is invoked by a function that is invoked within the Filter-context. Why is it like that? I thought that a function would be invoked in the invokers context.
function Filter() {
/**
* Other objects are set to this context (Filter)
*/
var search = function() {
/// Context here is window
}
var initialize = function() {
/// Context here is this (Filter)
this.searchBox = $("#search-box");
this.searchBox.keyup((function() {
/// Context here is this (Filter) due to the bind()
var newSearch = this.searchBox.val();
var previousSearch = this.filterValues.search;
if (newSearch !== previousSearch) {
if (newSearch.length === 0)
this.filterValues.Search = null;
else
this.filterValues.Search = newSearch;
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(search, 250);
}
}).bind(this));
}
initialize.call(this);
}
Usage:
this.filter = new Filter();
I think I found the answer:
this.searchTimer = setTimeout(search, 250);
is replaced with
this.searchTimer = setTimeout(search.bind(this), 250);
since setTimeout's context is window, and thus search got invoked in window.
Related
I need a way to invoke JS callbacks from a C library that uses contexts.
Here's an example:
const ctx1 = mylib_init();
mylib_set_event_callback(ctx1, () => {
console.log("EVENT");
});
Napi::FunctionReference cb;
bool done = false; // Used to prevent crash on multithreading.
// TSFN would obviously be used; this is just to shorten it.
extern "C" void onEvent(mylib_t* handle, void* userdata) {
if (cb != nullptr && !done) {
done = true;
cb.Call({});
}
}
Napi::Value MyWrapper::SetEventCallback(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::Object global = env.Global();
// info[0] = mylib_t* previously converted to a BigInt.
// info[1] = JS callback
mylib_t* handle = convertJSBigIntToHandle(info[0]);
r_cb = Napi::Persistent(info[1].As<Napi::Function>());
const auto ret = mylib_set_callback(handle, onEvent, nullptr);
return env.Null();
}
This works (the JS callback is run), but the problem is that the callback is global.
If I have ctx2 and call mylib_set_event_callback again, it will overwrite the callback from ctx1.
How can I convert this so that callbacks for ctx1 and ctx2 will both be called?
Your callback is global because it is in a global variable.
You should place the Napi::FunctionReference in the mylib_t structure.
If you cannot modify that structure, I see that you can pass a a context pointer in userdata - you are passing nullptr. Create dynamically a Napi::FunctionReference with new and pass this pointer so that you can have it in onEvent.
You should also properly the reference when the structure is destroyed or the function is replaced.
I'm working with a tree structure, so i need to do some pretty wonkey finds whenever I want to work my way from the leaves to the trunk, but I'm basically trying to create a function that I can pass a function to and apply / call / bind / something the original context so that I can see the variables that i had originally. An explaination would be awesome.
layerListView = Backbone.Marionette.CollectionView.extend({
updateSelectedModelsInTree: function () {
var col = myApp.request('someOtherCollection');
this.collection.startFromLeaves(function (m) {
this.updateSelected(m);
// col is undefined in here
}, this);
}
});
layerCollection = Backbone.Collection.extend({
startFromLeaves: function (doToModel, context) {
if (!this.models) return;
for (var i = this.models.length - 1; i >= 0; i--) {
var model = this.models[i],
tag = this.models[i].get('tag');
if (tag == 'branch') this.startFromLeaves(arguments);
doToModel.call(context, model);
}
}
});
so i'm stuck here, and all I want to do is to be able to see the col variable inside of the top function that is passed into startFromLeaves. I have no idea how to use call / bind / apply, but I'm guessing that my context is what is throwing everything off.
Check out the bind function on underscore.js
This allows you to pass a function that has the this context set. So for instance you could do
updateSelectedModelsInTree: function () {
var col = myApp.request('someOtherCollection');
this.collection.startFromLeaves(_.bind(function (m) {
this.updateSelected(m);
// col is undefined in here
}, this));
}
"this" inside your function will now always be the "this" that contains "updateSelectedModelsInTree"
Background
Basically, this code takes all of the audio tags on the page, and when one finishes it starts the next one in the DOM.
The Issue
When fnPlay is called I receive an Illegal Invocation error.
//THIS CODE FAILS
var lastAudio = null;
$('audio').each(function(index) {
var fnPlay = $(this)[0].play;
if (lastAudio != null) {
lastAudio.bind("ended", function() {
fnPlay();
});
}
lastAudio = $(this);
});
Now I am sure that the rest of the code is fine, because the following worked.
//WORKS GREAT!
var lastAudio = null;
$('audio').each(function(index) {
var lastAudioObj = $(this)[0];
if (lastAudio != null) {
lastAudio.bind("ended", function() {
lastAudioObj.play();
});
}
lastAudio = $(this);
});
Question
Can anybody explain why I couldn't store the play() function inside my variable fnPlay and call fnPlay(), but I could store the object and call the play() function on the object?
This is because of how the context of JavaScript functions work. The context (or this) inside a function is set when it's ran, not when it's set.
When you call lastAudioObj.play();, the play() function is called in the context of lastAudioObj. Inside play(), this is lastAudioObj, so everything works.
When you do fnPlay() however, it has no context. this inside the function will be null (or window). play() doesn't like that, so it throws an exception.
There are a few ways to fix this.
One is to call the function with .call() to manually set the context.
Set the variables like:
var lastAudioObj = $(this)[0];
var fnPlay = lastAudioObj.play;
Then call:
fnPlay.call(lastAudioObj);
You can also use .bind() to set the context when setting the variable.
var lastAudioObj = $(this)[0];
var fnPlay = lastAudioObj.play.bind(lastAudioObj);
Then you can just call it like:
fnPlay();
I wrote a quick custom extension for jQuery for a project I am working on. I am having a hard time understanding the scope of 'this' in a custom onChange method I would like implement. If left out the middle of my code where I am calling the webservice but if you checkout the last two methods, you will see where my problem is. I want to call the updateDetails method with the selected value changes. However, when that method is called within the onchange event, I obviously lose the scope of "this" as this.materialListResponse comes back as undefined in this context. Any help on helping me understand this would be greatly appreciated.
$.fn.appendMaterials = function (options) {
this.options = options;
//Set Default Options
this.defaults = {
typeID: '66E1320D-51F9-4900-BE84-6D5B571F9B80'
};
this.options = $.extend({}, this.defaults, options);
//
Code here to call web service and generate response XML
//
this.materialListResponse = $.xml2json(
$.parseXML($(this.materialListWebservice()).find("GetMaterialTreeResponse").text())).Materials.Material;
this.appendOptionString = function () {
var i = 0;
this.optionString = '<option>'
for (i = 0; i < this.materialListResponse.length; i++) {
this.optionString += '<option>' + this.materialListResponse[i].MaterialCode + '</option>';
};
this.append(this.optionString);
return this;
};
this.appendOptionString();
this.updateDetails = function () {
for (i = 0; i < this.materialListResponse.length; i++) {
if (this.materialListResponse[i].MaterialCode === this.val()) {
$('#table1 #MaterialDescription').val(this.materialListResponse[i].Description);
}
}
}
this.change(this.updateDetails)
};
pass the object this as data to the event:
this.change({that: this}, this.updateDetails)
and then you can access that in the scope of the event callback
this.updateDetails = function(event) {
var that = event.data.that;
...
}
RESOURCES
http://api.jquery.com/event.data/
The event handler will be called later, when you have exited the extension. It's called in the scope of the element, so this will be the element that has changed.
Copy the reference to the list to a local variable, and use that in the event handler:
var list = this.materialListResponse;
this.updateDetails = function() {
for (i = 0; i < list.length; i++) {
if (list[i].MaterialCode === this.val()) {
$('#table1 #MaterialDescription').val(list[i].Description);
}
}
}
By using the local variable in the function, the variable will be part of the closure for the function, so it will survive the scope of the extension method where it is declared.
When a method is called in JavaScript as a callback it behaves as a function. In this case "this" refers to the owner of this function, usually the Window object in a Web browser.
I'm new to object oriented javascript. I have a set up method that I want to a) check if an element is null and if so wait and call itself again and b) observe the click event of a button.
ErrorBox.prototype.setUpErrorBox = function(btnClientID) {
if (btnClientID == null) {
setTimeout("setUpErrorBox()", 1000)
return;
}
Event.observe(btnClientID, 'click', setValSummary);
}
I'm getting errors that setUpErrorBox and setValSummary don't exist (which they don't). How can I reference them? I tried this.setValSummary which didn't work.
In other words, how do I call the equivalent of a class's method from another method of the same class in javascript?
Use closures to hold on to your execution context:
ErrorBox.prototype.setUpErrorBox = function(btnClientID)
{
var box = this; // object context reference
if (btnClientID == null)
{
// use closure as event handler to maintain context
setTimeout(function() { box.setUpErrorBox() }, 1000)
return;
}
// again, use closure as event handler to maintain context
Event.observe(btnClientID, 'click', function() { box.setValSummary() });
}
See also: JavaScript Callback Scope