What is the recommended way to pass cached jQuery references, e.g. $domContainer in var $domContainer = $('#container'); to functions as a callback if the functions are defined before and outside of $(document).ready()?
Example:
<script src="/plugins.js"></script>
In this external file of re-usable functions
function rowAction ( event ) { // how do I get context here?
// how can I access $domTable and $domFilters
// I can access $(event.target) and traverse the dom
// back to $domTable, but $domTable is not defined
// in the external file, although a reference is cached
// in the local scope of $(document).ready();
// likewise, $domTable could be accessed through event.delegateTarget
// however, how can I pass $domFilters, and other vars?
}
In the main script
<script src="/scripts.js"></script>
The standard document ready
$(document).ready(function(){
// Cached References
var $domFilters = $('#form .filter'), // more than 1 reference returned
$domTable = $('#results'); // 1 reference returned
$domTable.on('click','.menu',rowAction);// reference function in plugins.js
// how do I pass $domFilters to rowAction to avoid dom lookups?
// I could pass $domFilters to a plugin like $(...).plugin({f:$domFilters});
// if it had optional arguments, but if it's not a plugin, what's the
// equivalent way to do it?
});
Would the way to approach this be to use an inline function to wrap the callback function name?
Any pointers to a standard practice would be welcome too.
You can follow a modular approach by defining a NameSpace. Then you won't have to use ready.
//These four will be open
var objects, handlers, bindings,
NameSpace = {
//This is where you cache references
objects: {
domcontainer: $('.domcontainer')
},
//Do the events to handlers bindings
bindings: function(){
domcontainer.on('click', handlers.clickCallback)
}
//The actual handlers
handlers: {
clickCallback: function(e){
//Do something
}
},
//Initial things
init: function(){
objects = this.objects;
handlers = this.handlers;
//This will start the handler binding.
this.bindings();
}
};
$(function () {
NameSpace.init();
});
If you are adding objects on the fly, then inside objects you can add references as functions that return the actual object reference. Whenever you need to refer an object, it will be available already hence avoiding DOM look up.
If you are looking to access $domTable you can use the event.delegateTarget property of the event object without having to traverse the dom. You will have to wrap it in a jQuery object though.
Edit
A standard way of passing the context and extra properties to an external function would be to use call() or apply() jQuery has it's own wrapper for that behaviour also called proxy()
In your example with $domTable the context is already passed through as the target of the selector so everything you need would be available to you.
In your $domFilters example there is no event object to pass as the context since event are mapped to actual events in the dom and all you have there is a collection so you couldn't use that function since it relies on the event object.
If I was calling another function though from a collection whilst passing the context I would use something like this.
$domFilters = $('#form .filter');
$domFilters.each(function(){
// Call the external function passing the jQuery wrapped item
// as the context.
externalFunction.call($(this));
});
// Your external function
function externalFunction(){
// 'this' would now refer to the context passed in call.
// i.e your $(.filter) item.
}
Your utility function has to be designed to be able to handle whatever is passed to it as context plus any additional arguments though.
Related
I have a class that I use to load external resources via an XMLHttpRequest (this is for WebGL) so I'm loading models, shaders etc. My plan was to put a loading display up whilst it did all these requests and then when it's finally complete I want it to run a callback function from the original function that created it. However, I'm getting strange results when I try to run that call back (such as it has no access of any of the objects within the class that did the loading).
I can get around this problem by passing "this" into the loading class and then doing
self = this;
promise(self.callback());
but I'd much rather specify the function that I want it to callback to after its done the loading. Does anyone know if this can be done? My code looks like this:
Main Class
this.loadingClass = new LoadingClass(this.LoadingComplete, resources);
Main.prototype.LoadingComplete = function()
{
// Returns undefined if i specify the callback function instead of just "this"
console.log(this.loadingClass.anyOfTheMembersOfThisClass);
}
Loading Class
LoadingClass = function(callback, resources) {
..
Promise.all(resources).then(function(loadedResources)
{
..
callback();
});
}
When you pass the function object as
(this.LoadingComplete, resources)
the object to which it was bound, will not be passed. So, only the function object LoadingComplete is passed to LoadingClass and when it is invoked as
callback()
the this value will be undefined (in strict mode).
To fix this,
you need to bind the this object, like this
new LoadingClass(this.LoadingComplete.bind(this), resources)
if your environment supports ES2015's Arrow functions
new LoadingClass(() => this.LoadingComplete(), resources);
In both these cases, when the LoadingComplete is invoked from LoadingClass, the this will be retained.
You are detouching callback (read about "this") function from the root object, so of course it loses context. Specify bindingContext explicitly with Function.prototype.bind method:
this.loadingClass = new LoadingClass(this.LoadingComplete.bind(this), resources);
So I have most of my functions and variables organized into small object-based modules, like so:
module1: {
someVariable: false,
someFunction: function(e) {
do some stuff using someVariable
},
someFunction2: function(e) {
do some other stuff
}
}
And I call these functions as callbacks during various events, like so:
$(function() {
$('.thing').on('mouseenter', module1.someFunction);
}
Now, from within someFunction, I would expect the 'this' keyword to refer to the object in which the function is contained. Instead, it refers to the DOM element that triggered the event that fires the function. Is there anyway I can get access to, say the someVariable variable in the function's containing object other than writing module1.someVariable?
The shortest answer is to try this:
$(function() {
$('.thing').on('mouseenter', function(e) {
module1.someFunction(e);
});
}
The 'this' value is only set to the object the method is attached to if the method is invoked directly on the object:
module1.someFunction(); // direct invocation, 'this' will be set properly
var tempFunc = module1.someFunction;
tempFunc(); // the function was removed first. 'this' will NOT be set
In your case, you are pealing the method off of the object and handing it to an event handler. The event handler doesn't know about the object and doesn't perform a direct invocation.
In fact, the event handler explicitly overrides the context because that is how the jQuery API is defined. You have to explicitly override it back if you want the behavior you're talking about.
Using a library like underscore.js you could also bind the function as you pass it off to the event handler.
$(function() {
$('.thing').on('mouseenter', _.bind(module1.someFunction, module1));
}
I believe that Object.bind is supposed to be natively supported in the future with no libraries, but you can't yet rely on the older browsers to support it.
I am study the udacity's course and encounter a problem.
https://www.udacity.com/course/viewer#!/c-cs255/l-49464373/e-73862317/m-73162952
function xhrGet(reqUri,callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", reqUri, true);
xhr.onload = callback;
xhr.send();
}
var TILEDMapClass = Class.extend({
// Boolean flag we set once our map atlas
// has finished loading.
fullyLoaded: false,
//-----------------------------------------
// Load the json file at the url 'map' into
// memory. This is similar to the requests
// we've done in the past using
// XMLHttpRequests.
load: function (map) {
// Perform an XMLHttpRequest to grab the
// JSON file at url 'map'. We've provided
// the xhrGet function from the optional
// unit for you to use if you want.
//
// Once the XMLHttpRequest loads, set the
// 'fullyLoaded' flag to true.
//
// YOUR CODE HERE
xhrGet(map, function(){
this.fullyLoaded = true;
});
}
});
// We define a single global instance of our
// map for the rest of our game code to access.
var gMap = new TILEDMapClass();
the link says that it use gMap.load.apply(gMap, [jsonURL]);
http://forums.udacity.com/questions/100058023/scope-of-this#cs255
but I think that inspite the fact using the called mothod.(The load will belong to gMap)
But because
xhr.onload = function(){
this.fullyLoaded = true;
}
is a method belong to the xhr object,
and the this is inside an an anonymous function
the this should reference the xhr not gMap.
Why the this reference gMap?
this is funny within closures. You have to remember that the this keyword will usually refer to the owner of the method. Usually the caller (window for global functions) but when a method is called as a property of an object, this will refer to the object itself.
See this: "this refers to the parent object inside function code if the function is called as a property of the parent." Understanding this
The rules directly from Understanding this:
By default, this refers to the global object.
When a function is called as a property on a parent object, this
refers to the parent object inside that function.
When a function is called with the new operator, this refers to the
newly created object inside that function.
When a function is called using call or apply, this refers to the
first argument passed to call or apply. If the first argument is null
or not an object, this refers to the global object.
this doesn't necessarily mean the function or object it's being called on, if you're used to using jQuery and are confused by this, the jQuery methods actually set this on all of its functions for convenience by calling one of these two functions which set this to the caller:
call(object, arg0, arg1...);
apply(object, args[]);
So basically, unless the function is setting this by calling one of the above functions, it will be set to some outer function/object or window.
"this" in a javascript function has nothing to do with the object to which the function belongs, but what object it is executed against
Contrast with Java, where those are the same because a method is truly part of an object and cannot exist without one (not considering statics).
For example:
var blah = {
test: function () {
console.log('test');
}
};
var f = blah.test;
var bleh = {
test: blah.test
}
If I then make each of these three function calls, what is "this" pointing to in each call?
blah.test(); // this points to blah
f(); // this is null (or undefined, not sure which)
bleh.test(); // this is bleh
I can also use Function.call to call a function object in the context of any object: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
f.call(window); // this is window
Understanding "this" is difficult when working with callbacks because the callback function is usually invoked by some other library (like jquery for instance) and their API may or may not make a guarantee to what "this" refers to. What you can do as a work-around:
someAsyncFunction(function () {
bleh.test();
});
That will ensure the function you care about is called with a predictable "this" reference.
I have written two functions in JavaScript code as follows
Manager = FormManager.extend({
First: function () {
var response = this.Second("Feature"); //I'm able to get the alert
//I have added a click event handler
$('#element').on('click', function(){
var newResponse = this.Second("Bug"); //The alert is not poping
});
}
Second: function (type) {
alert(type);
//Performs certain operation
}
});
Error: Uncaught TypeError: Object #<HTMLButtonElement> has no method 'Second'
I also tried without using this keyword like:
Second("Bug") // Error: There is no method
Whereas this a simplified format (in-order to show a simple example) on my program that I'm playing with. I'm struggling to find out the reason.
Can someone direct me to the right path?
You are using incorrect this. try this way. this inside the handler represents #element not the context of the function itself.
var self = this; //cache the context here
$('#element').on('click', function(){
var newResponse = self.Second("Bug"); //Access it with self
});
Also i think you are missing a comma after First function definision and before Second function.
Fiddle
The reason being the callback you give gets invoked from within the context of the element so your this context changes. this context refers to the context from where the callback was invoked. But there are other ways to get around this like using $.proxy while binding your callback with jquery, using EcmaScript5 Function.prototype.bind etc. But ideally you don't want to do that because most of the cases you would need the context of the element there inside the handler.
Every time you use the this context variable in a function you have to consider what its value is.
Specifically that value will be whatever value the caller specified, whether by using myObj.mymethod(...), or mymethod.call(myObj, ...), or mymethod.apply(myObj, [ ... ]).
When your anonymous function $('#element').on('click', ...) is invoked jQuery will set the context to the HTML DOM element - it's no longer referring to your object.
The simplest work around is to obtain a copy of this outside of the callback, and then refer to that copy inside the closure, i.e.:
var that = this;
$('#element').on('click', function() {
// use that instead of this, here
console.log(this); // #element
console.log(that); // your object
});
Another method is using Function.prototype.bind:
$('#element').on('click', (function() {
console.log(this); // your object
}).bind(this));
or with jQuery you can use $.proxy for the same effect, since .bind is an ES5 function.
I actually prefer the var that = this method, since it doesn't break the jQuery convention that this refers to the element associated with the event.
I have a 2000 line jquery file, I just broke up the file into smaller ones, If I have a function in the first file, that file # 2 is referring to, it's coming up undefined.
Every file is is wrapped in a jquery ready function, What's the best way to do this?
If the function in question is declared within the scope of the ready handler, it won't be accessible to any other code, including other ready handlers.
What you need to do is define the function in the global scope:
function foo()
{
alert('foo');
}
$(document).ready(function()
{
foo();
});
P.S. A more concise way of adding a ready handler is this:
$(function()
{
foo();
});
Edit: If the contents of each of your divided ready handlers rely on the previous sections, then you can't split them up, for the reasons outlines above. What would be more sensible would be to factor out the bulk of the logic into independent functions, put these in their own files outside the ready event handler, and then call them from within the handler.
Edit: To further clarify, consider this handler:
$(function()
{
var foo = 'foo';
var bar = 'bar';
alert(foo);
alert(bar);
});
I might then split this up:
$(function()
{
var foo = 'foo';
var bar = 'bar';
});
$(function()
{
alert(foo);
alert(bar);
});
The problem with this is that foo and bar are defined in the first handler, and when they are used in the second handler, they have gone out of scope.
Any continuous flow of logic like this needs to be in the same scope (in this case, the event handler).
Function definition should not be wrapped in another function. Not unless you really want that function definition to be private. And if I understand correctly that's not your intention.
Only wrap function invocation in the jQuery ready function.
If you're worried about your functions clashing with third party function names then namespace them:
var myFunctions = {}
myFunctions.doThis = function () {}
myFunctions.doThat = function () {}
But really, you only need to worry about this if you're creating a mashup or library for others to use. On your own site YOU have control of what gets included in javascript.
Actually, for performance reasons, it may be better to keep it in one file; multiple requests actually can take up more bandwidth... but as separate files, you would need to order them in a particular order so that there is a logical sequence. Instead of having everything in a document.ready, have each script define a method, that the page will execute within its own document.ready handler, so that you can maintain that order.
Most likely the reason it's coming up undefined is because when you have separate ready calls, the scope of the code inside those calls is different.
I would reorganize my code. Any shared functions can be attached to the jQuery object directly, using $.extend. This is what we do for our application and it works well.
See this question. Hope it helps.
Everyfile shouldnt have a ready function. Only one file should have the ready function and that should be the last file.
"wrapped in a jquery ready function" is nothing else than binding stuff to the ready event that is fired when jQuery thinks the DOM is ready.
You should only bind methods that is depending on the DOM to the ready event. It doesnt matter how many binds you make, all of the methods will be executed in the binding order in the end.
Functions provide scope in JavaScript. Your code in the jquery.ready is an anonymous function, so it is unaware of the other scopes. remove the wrappings for those JavaScript functions and declare them as regular functions, a la
$(document).ready(function ()
{
functionFromFile1();
functionFromFile2();
};