Call jQuery on from apply - javascript

I am writing a small library that will take care of adding and removing events. I am simply attempting to pass the parameters of a method to jQuery on.
Is there any reason that I should not be able to do this?
# within some object
...
on: (el, args...) ->
$(el).on.apply(this, args)
The way I am using this may not be correct, but I don't believe it is the problem.
Which would then be called like so:
SomeObj.on('#hi', 'click'
-> console.log('clicked element')
)
I get the error Uncaught TypeError: elem.each is not a function
For some reason this is totally stumping me.

The problem is the wrong usage of this, despite your assumption.
The issue is that when you call a member function ("method"), it usually operates on some object, it's "context". Invoking a function via apply, call or through bind doesn't have the context passed in automatically because that gets lost when you access the properties of the function (apply in this case). In order to overcome this, it off required to set the relevant this attribute. In your case, this means passing the $(el) object to the apply() function.

Related

Can I find a JQuery function inside some object defined into the JavaScript window global object?

I am pretty new in JavaScript and JQuery and I have the following doubt.
I know that if I open the FireBug console typing window I see the content of the global object that in a JavaScript application that run into a browser is the current browser tab (the window).
Ok, so in this global object I can see all the global variables and all the global functions (the variables and the functions that are not defined inside another function).
So I have a page in which I link my .js file.
This file will contain some pure JavaScript function like this:
function myFunction() {
.................................
.................................
.................................
}
And so I will see the myFunction function as a field of the window global object because this function is global (it is not defined inside another function). This is perfectly clear to me.
So, into the .js file linked inside my page I also have something like this:
$(document).ready(function() {
$("#myButton").click(function() {
............................................
............................................
DO SOMETHING WHEN THE BUTTON HAVING id="myButton" IS CLICKED
............................................
............................................
});
});
So, this is a JQuery code and it should work in this way (correct me if I am doing wrong assertion).
There is the $ that is the JQuery objet (or what is it?).
On this JQuery object I call the ready() function that is the function that perform its inner callback function when the DOM is completly rendered.
So the inner callback function contain the:
$("#myButton").click(function() {...DO SOMETHING...});
the select a button having id="myButton" and add to it the click event listerner that itself define an inner callback function that is performed when the button is clicked.
Is it true?
Ok...so I think that all these stuff is not direcctly in the global object because it is not directly defined into my .js file but have to be in some memory space dedicate to JQuery.
So looking inside the window object inside the FireBug console I found two objects:
$: that I think is the JQuery object...so I think that my previous custom JQuery function have to be here but I can't find it.
JQuery: that is another object that is inside the window global object.
So, my doubt is: when, inside the ready() function I declare something like
$("#myButton").click(function() {...DO SOMETHING...});
where I can find the definition of the function() {...DO SOMETHING...} inside some object defined inside the window global object? Can I find it? Or am I missing something?
jQuery stores its event-related data in a data object events applied to each element. You can use $._data() to grab this info:
$._data($('#myButton')[0], 'events')
or
$._data(document.getElementById('myButton'), 'events')
To get the callback function that you applied for your button's click listener, you can simply grab the handler. For example:
$("#myButton").click(function () { console.log('clicked'); });
var eventsInfo = $._data(document.getElementById('myButton'), 'events');
console.log(eventsInfo.click[0].handler);
The above should print out "function () { console.log('clicked'); }".
Keep in mind that there is no public documentation available for $._data(), although it is a neat thing to know!
The following blog post mentions $._data() when jQuery v1.8 was released but does warn about this:
Note that this is not a supported public interface; the actual data structures may change incompatibly from version to version.
That was back in 2012. To this day, it seems to be working fine with the latest 1.x and 2.x versions, so I don't see this going away anytime soon.
This is an anonymous function, basically a piece of unique code that you don't really want to write a named function for.
It's a one-time use-case or rather it's used to bind custom click events without littering the global object with variables.
What an anonymous function does is the exact opposite of what you are asking since you can't find it in the global object (anonymous function).
.click is a function defined in $.fn and since $ is part of the window object you could traverse there to find click e.g. window.$.fn.click would be the path to the source of $(...).click(func...) but an anonymous function is a function that gets set and then forgotten (more or less).
After all, you're not giving it a name so there is no reference it can point to which is exactly what this is.
If you use a named function as an argument to another function it's called a callback function
An anonymous function is basically a nameless callback function, a callback function is a normal function that can be passed to other functions as a callable argument - this normal function will then internally use .call() or .apply() to execute the supplied callback which is what jQuery for instance does when you bind a click
The good thing here is that you're not missing anything at all, as a matter of fact - you're asking the right question because this will look like magic if you're just starting out but once you get the hang of it it's easy to understand and use (and misuse so be careful!)
If you'd like to know how this construction works you could always build your own function that accepts a callback / anonymous function e.g.
function result_based_on_callback(a, b, fn) {
fn.call(null, a, b);
}
The above function takes two parameters and a function, it will call the function and supply the two parameters to it (the null is the context of this which is a different kind of question :))
If we were to use the above construct to do a calculation we could do so like this:
console.log(result_based_on_callback(1, 2, function(a, b) { return a + b; }));
This would return 3, you can also do this with a normal function that would otherwise take two numbers and add them - it works the same except for just passing in the function name rather than the body
like this:
function do_add(a, b) {
return a + b;
}
console.log(result_based_on_callback(1, 2, do_add));
Which will do the exact same.
I hope this allows you to understand a bit of how this works, good luck!

Polymer this-pointer

I'm using the Polymer framework and I really enjoy it. But one thing I don't get is the confusion with the this-pointer. When functions get called from for example a button in your custom component the this-pointer points to the custom component. Very logical. But when your function within a custom component is called from something external, for example a callback from an library or a call from another component the this-pointer is something totally different. Why is it in this case not pointing to the custom component where the function is in?
Javascript is a bit of a weird bird when it comes to resolving this, often not doing what you would like. The only saving grace is that it's easy to explain and understand.
A function's this value is set by how it is called. Suppose you have a value val with a method method. If method is called like val.method() then in that call to method then this is val. If you instead do var theMethod = val.method; theMethod(); then for that call, this is something else (the global context object, in browsers this is window).
The solution fortunately is simple. There's a method on functions called bind that returns a new function that has the this immutably baked in. So var theMethod = val.method.bind(val); theMethod() has this bound to val.
In the future for many cases we'll be able to use ES6 Arrow Notation to get this behavior baked in at function definition time, but for now, when passing a method around (e.g. to register an event handler) be sure to bake the this in explicitly with bind.

Weird issue with "this" losing its value in a validation function in aspect.around. Or, aspect.around is not working as expected

Note: please keep in mind that this is not a generic question on the use of this on javascript. This is about aspect.around malfunctioning (it's meant to set the scope for the call, and it doesn't). The question is: why is aspect.around malfunctioning? this question needs you to read carefully how to reproduce and do so with the fiddle provided!
I had to shred my app to pieces in order to make the problem fit in a fiddle.
So here it is:
http://jsfiddle.net/mercmobily/THtsv/1/
It's a simple form, with validation:
Type something in the textbox: the validation method of the widget will be called.
Then press the submit button: validation will fail, and aspect.around will be called to wrap something around the validation method.
At that point, try to type anything in the textbox again: It will come back with an error, as the validator will fail because of "this" being set to "window" instead of the widget.
So, once the aspect is added, the validator stops working. Basically, the value of "this" gets lost. Now:
aspect.around() is meant to run the new validator in the right scope (obviously) and it's failing to do so
I can "fix" this problem by changing the call to the validator into this: return originalValidator.call(this, value); However, it doesn't answer the question "Why is 'this' lost?"
If you backtrace the code, you will see that aspect.around() is doing what it normally does... but it must be doing something wrong
So, the question: why is dojo.around() malfunctioning, not setting this to the passed object's scope?
Merc.
It is not very easy to follow what exactly you're asking. From your jsFiddle, I see this comment so I'll attempt to answer the question you pose here:
// QUESTION: FIND OUT WHY WE NEED THIS "call"
return originalValidator(value);
// return originalValidator.call(this, value);
The answer to why you need the .call here in order to preserve the value of this is as I described below in the generic description of how this works when making a function call.
When you make an ordinary function call as in this statement:
return originalValidator(value);
The value of this is set back to window. That's how javascript works. If you want to preserve the current value of this in that function, you have to specify that you want a particular value of this set using .call() or .apply() or an obj.method() call. The value of this in an ordinary function call is NOT bound to the function. It's set by the caller and can be anything the caller wants. If you don't specify it, then javascript sets this to window and that is exactly what is happening in your code.
Here's the generic description of how the value of this is set and this generic description applies in your specific case.
The simple rule is that the value of this is reset on every single function call in javascript. If it's just a plain function call, then this is set to the global object (which is window in the browser environment). So any simple function call will always set this to window.
If you make a method call like obj.method(), then this will be set to point to the obj while in the method().
If you use func.apply(a, b) or func.call(a, b) then you can explicitly control what this is set to via the value of the first argument to .apply() or .call(). See this MDN doc here or here for more info on .call() and .apply().
this is the current context. By default it's the global object (or null in strict mode), when calling a function on an object (foo.bar()) it's set to that object. When using .call() or .apply() to call a function it's set to whatever first argument was passed to that function.
This means that you cannot assume that this is still the same when you go into another function - even if you define that function in a context where this is what you want.
The most common approach is adding var self = this; and then using self instead of this in the inner function - since self is a normal variable it will be in the function's closure and not be affected by this being bound to something else in the function.

JQuery\Javascript - Passing a function as a variable

I was just curious if I could pass a function as a variable. For example:
I have a function
$('#validate').makeFloat({x:671,y:70,limitY:700});
I would like to do something like this:
$('#validate').makeFloat({x:function(){ return $("#tabs").offset().left+$("#tabs").width();},y:70,limitY:700});
This does not work, but ideally every time the variable was accessed it would compute the new value. So if the window was resized it would automatically adjust as opposed to a variable passed in being static. I realize I can implement this directly inside the function\widget, but I was wondering if there was some way to do something like the above.
The concept of this is independent of the plugin. I am talking about the function being "cast" as a variable.
Yes, you can pass an object which will invoke some function when its property is read (this is called a getter), but it is not cross-browser compatible. For example, this will (probably) work in IE9:
var o = {y:70, limitY:700};
Object.defineProperty(o, 'x', {get: function() {return 671;}});
$('#validate').makeFloat(o);
There are other syntaxes for other browsers such as __defineGetter__ for Firefox, and some browsers don't have this functionality at all. So it is practically useless unless you can fully control the environment where your code runs.
This won't work unless x is invoked (obj.x(), instead of just obj.x).
To make it work, the makeFloat() code must check the type of x, and if it's a function, invoke it.
I see what you're trying to do, but it won't work. Why? makeFloat expects the value to be non-function type. It probably uses that value directly. To actually execute the function, makeFloat needs to do x() or even x.call(...) or x.apply(...), which it most certainly isn't doing.
To answer your other question i.e., can you pass functions as variables, the answer is yes. In fact, this is the way callbacks and closures are handled in Javascript. For example, in jQuery when you bind an event handler you are passing in a function as a parameter:
jQuery("#myInputId").click(function() {
...
...
});
Another way that parameters are passed in are as object attributes, for example in jQuery.ajax:
jQuery.ajax({
...
success: function(data) {
},
...
});
In both cases, click and ajax both understand and expect the parameter to be a function and not just a regular variable. For example, assuming you had an object that maintained a list of integers and you had a method called addElement(int), which expected an int parameter, you wouldn't pass in a String. It works the same way in Javascript, except for the fact that the language is not strongly typed. This is why you don't really get a type-mismatch error unless the function explicitly checks the type and throws an exception. This is generally a good practice in such language; I try to do this in the Javascript code that I write.
I've done this with string variables. You'll need to exploit the toString function.
function RefString(fn) { this.toString = function() { return String(fn()); }; }
You can use it like so:
$("#someDiv").somePlugin({optionValue: new RefString(MyFunc), ... });
function MyFunc() {
return new Date().getYear().toString();
}
It works by setting optionValue to a new OBJECT, not necessarily a function. Then anything that reads this object will ask for a value, which by default is the result of the toString function. We simply override the default behavior by executing a function that is specified when the object is constructed.
I'm not sure how it will work for EVERY plugin, but it works when a string or number is expected.
How do you mean "doesn't work"?
It looks like it should compile and run. But what happens is it executes the function and sets the value no different than if you used a constant, or called a function that wasn't inline.
What you need to do is put this line of code in an event that fires when the window is re-sized.
It looks like makeFloat is from a jQuery plugin - are you sure that the plugin is aware that 'x' can be a function and will execute it properly? From the jQuery site, it looks like it only is able to comprehend a number value or 'current' as a string, not a function.
You can pass functions as variables, yes - but that's not actually what you're asking.
What it looks like your asking is "can I set a DOM property to the result of an expression?" to which the answer is "no". (Note - not outside of browser-specific behavior such as IE's CSS Expressions - which have been deprecated in IE8 anyway)
You'll need to bind an event handler to window.onresize and use a function to update the sizing yourself.
In order for a function to be executed from a variable, it has to be called, like so:
$.option.callback.call();
Where option is the containing variable, callback is the function and call executes the function.
It's not like you don't have options though. You can set it up so that the returned value of that function is executed from the line itself. Or you can set it up in the alternative manner that you described.
You need to invoke that function so that it returns the actual value you're looking for. So you're not actually passing in a function, you're invoking it and it's immediately returning a value. For example:
$('#validate').makeFloat({
x:function(){
return $("#tabs").offset().left+$("#tabs").width();
}(),
y:70,
limitY:700
});
Notice the extra () after the function call. This invokes the function immediately, thus returning the value you're looking for.
Note that x doesn't "compute new value" when is accessed (read), but when the function is called, i.e. x(). As Chad mentioned, this is how you can automatically execute a function when windows is resized:
$(window).resize(function() {
// do something
});
[Update] After re-reading your question, I think you may be thinking overcomplicated – isn't this what you are looking for?
$('#validate').makeFloat({
x: $("#tabs").offset().left + $("#tabs").width(),
y: 70,
limitY: 700
});

Javascript function parameters

I am working on a project which involves the ExtJS library and I came upon this piece of code which did not make sense (but it works). Please help :(.
TreePanel.on('click', showDocumentFromTree);
function showDocumentFromTree(node) {
if (TreePanel.getSelectionModel().isSelected(node)) {
dataStore.baseParams = {
node : node.id,
limit : 10
}
dataStore.load({
params : {
start : 0
}
});
}
};
So the function definition for "showDocumentFromTree" has a parameter called "node" but when the code calls it, it did not pass in anything. Also, the object "node" is not a global (as far as I know).
So I'm confused on how that works? Is this some magic that Javascript has?
Also, when I do a console.debug to print "node" it has stuff in it. (console.debug for FireBug)
Thank you for your time,
J
When the code is doing `TreePanel.on('click', showDocumentFromTree)', it isn't calling showDocumentFromTree, it's passing the function showDocumentFromTree as an argument. It will then be called later, by the onclick event handler that it's being set up for, and it will be passed its node argument then.
The first line binds the showDocumentFromTree function to the click event. What is passed to TreePanel.on is a reference to the showDocumentFromTree function, not the call itself.
When an event fires, the bound function(s) will be called, with the triggering object as the first parameter. In this case, it will be the DOM node that was clicked.
To illustrate, the first line can be rewritten to:
TreePanel.on('click', function(node) {
showDocumentFromTree(node);
});
OK, maybe this is not much clearer, but you can see that it actually passes a function as argument to the on method, rather than calling the method itself.
TreePanel is a class/component in Extjs. In your first line:
TreePanel.on('click', showDocumentFromTree);
You are assigning a click handler to the TreePanel class. Meaning, whenever the TreePanel is clicked, it will call your showDocumentFromTree function. Part of the Click Event for the TreePanel is to pass the TreeNode that initiated, or was the item, that "caused" the click event.
To see how this functionality works, look at the Ext.tree.TreeEventModel class specifically the initEvents, delegateClick, and onNodeClick functions.
In this case, the parameter to showDocumentFromTree is a magic parameter that is supplied by the browser when the user clicks on the element to which the action is attached. In this case, node will refer to the TreePanel. Javascript - The this keyword explains more detail about this mechanism. It is probably more common to use the parameter name this than node as in your example.
Wthout repeating what other posters have said about the browser supplying the arguments to the function, I wanted to make a general note about javaScript as a language. JavaScript, unlike languages like C++ and Java, does NOT respect parameters defined in the function signature. This means you can have a function like:
function doSomething(myParam){
... Does Some Stuff
}
Then call it in any manner below:
doSomething();
doSomething(foo);
doSomething(foo, bar);
doSomething(foo, bar, baz);
etc..
If it is called without parameters defined in the signature, the missing parameters will be undefined. Extra parameters can only be accessed by the args array that all functions have.
I know this wasn't specific to your question but I thought it might be good for context and general interest.

Categories

Resources